diff --git a/.github/ISSUE_TEMPLATE/add-a-user-story.md b/.github/ISSUE_TEMPLATE/add-a-user-story.md new file mode 100644 index 00000000000..699d884cb9d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/add-a-user-story.md @@ -0,0 +1,12 @@ +--- +name: Add a User Story +about: This template helps add a user story as an issue to be tracked. +title: As a user, I can ... +labels: type.Story +assignees: '' + +--- + +... so that ... + +Command: `...` diff --git a/.gitignore b/.gitignore index 5e59b862ba4..b556826d375 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /.gradle/ /build/ src/main/resources/docs/ +/bin/ # IDEA files /.idea/ @@ -19,3 +20,10 @@ src/test/data/sandbox/ # MacOS custom attributes files created by Finder .DS_Store + +# VS Code files +.project +.settings/org.eclipse.buildship.core.prefs +.vscode/settings.json +.classpath +.settings/org.eclipse.jdt.core.prefs diff --git a/README.adoc b/README.adoc index e36efe534bb..2af9f2d21ee 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,10 @@ -= Address Book (Level 3) -ifdef::env-github,env-browser[:relfileprefix: docs/] - -https://travis-ci.org/se-edu/addressbook-level3[image:https://travis-ci.org/se-edu/addressbook-level3.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level3[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level3?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level3/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level3?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level3&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] +https://travis-ci.org/AY1920S2-CS2103T-W12-4/main[image:https://travis-ci.org/AY1920S2-CS2103T-W12-4/main.svg?branch=master[Build Status]] +https://ci.appveyor.com/project/AdarshChugani/main[image:https://ci.appveyor.com/api/projects/status/coce69u02f8j3qte?svg=true[Build Status]] +https://coveralls.io/github/AY1920S2-CS2103T-W12-4/main?branch=master[image:https://coveralls.io/repos/github/AY1920S2-CS2103T-W12-4/main/badge.svg?branch=master[Coverage Status]] +https://app.codacy.com/gh/AY1920S2-CS2103T-W12-4/main?utm_source=github.com&utm_medium=referral&utm_content=AY1920S2-CS2103T-W12-4/main&utm_campaign=Badge_Grade_Dashboard[image:https://api.codacy.com/project/badge/Grade/26ab505f4302490cb6160c093889381c[Code Quality]] += CookBuddy Recipe Manager +ifdef::env-github,env-browser[:relfileprefix: docs/] ifdef::env-github[] image::docs/images/Ui.png[width="600"] @@ -15,22 +14,22 @@ ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. +* CookBuddy Recipe Manager is a desktop application for managing recipes. +* It has a Graphical User Interface (GUI) but most of the user interactions happen through a Command Line Interface (CLI). +* CookBuddy is designed primarily for advanced computer users who prefer typing over using a mouse. == Site Map * <> * <> -* <> * <> * <> == Acknowledgements -* Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by -_Marco Jakob_. +* This project has been adapted from AddressBook-Level3 sample application created by the SE-EDU initiative at https://se-education.org +* Some parts of CookBuddy were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX +tutorial] by _Marco Jakob_. * Libraries used: https://openjfx.io/[JavaFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/junit-team/junit5[JUnit5] == Licence : link:LICENSE[MIT] diff --git a/build.gradle b/build.gradle index 93029ef8262..e959e58bdeb 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ plugins { } // Specifies the entry point of the application -mainClassName = 'seedu.address.Main' +mainClassName = 'cookbuddy.Main' sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 @@ -27,6 +27,7 @@ repositories { checkstyle { toolVersion = '8.1' + // ignoreFailures = true; } jacocoTestReport { @@ -37,10 +38,6 @@ jacocoTestReport { } } -test { - useJUnitPlatform() -} - dependencies { String jUnitVersion = '5.4.0' String javaFxVersion = '11' @@ -64,10 +61,12 @@ dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: jUnitVersion testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion + + implementation group: 'org.jfxtras', name: 'jmetro', version: '11.6.9' } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'CookBuddy.jar' destinationDir = file("${buildDir}/jar/") } @@ -98,6 +97,8 @@ tasks.coveralls { } test { + useJUnitPlatform() + ignoreFailures(true) testLogging { events TestLogEvent.FAILED, TestLogEvent.SKIPPED @@ -133,8 +134,8 @@ asciidoctor { idprefix: '', // for compatibility with GitHub preview idseparator: '-', 'site-root': "${sourceDir}", // must be the same as sourceDir, do not modify - 'site-name': 'AddressBook-Level3', - 'site-githuburl': 'https://github.com/se-edu/addressbook-level3', + 'site-name': 'CookBuddy', + 'site-githuburl': 'https://github.com/AY1920S2-CS2103T-W12-4/main/', 'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) ] @@ -153,3 +154,4 @@ task copyStylesheets(type: Copy) { asciidoctor.dependsOn copyStylesheets defaultTasks 'clean', 'test', 'coverage', 'asciidoctor' +//defaultTasks 'clean', 'test', 'asciidoctor' diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml index dabcd1ff4e3..84a5513294f 100644 --- a/config/checkstyle/suppressions.xml +++ b/config/checkstyle/suppressions.xml @@ -6,4 +6,5 @@ + diff --git a/desktop.ini b/desktop.ini new file mode 100644 index 00000000000..a2a78aadc3f --- /dev/null +++ b/desktop.ini @@ -0,0 +1,6 @@ +[.ShellClassInfo] +IconResource=C:\Windows\System32\SHELL32.dll,111 +[ViewState] +Mode= +Vid= +FolderType=Generic diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index 458e6134f45..7a6adeed397 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,53 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 3 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + -{empty} + +CookBuddy - This recipe managing application was developed by the https://github.com/AY1920S2-CS2103T-W12-4/main[AY1920S2-CS2103T-W12-4] team. + + We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] +=== Adarsh Mohandas Chugani +image::adarshchugani.png[width="150", align="left"] +{empty}[https://github.com/AdarshChugani[github]] [<>] -Role: Project Advisor +Role: Team Lead + +Responsibilities: UI ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +''' -Role: Team Lead + -Responsibilities: UI +=== Kevin Sum +image::kevinswk94.png[width="150", align="left"] +{empty}[https://github.com/kevinswk94[github]] [<>] -''' +Role: Developer + +Responsibilities: Dev Ops -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +''' +=== Qi Mingsi +image::e0316059.png[width="150", align="left"] +{empty}[http://github.com/e0316059[github]] [<>] Role: Developer + -Responsibilities: Data +Responsibilities: UI ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Sharadh Rajaraman +image::sharadhr.png[width="150", align="left"] +{empty}[http://github.com/sharadhr[github]] [<>] Role: Developer + -Responsibilities: Dev Ops + Threading +Responsibilities: Documentation ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Zain Alam +image::muhd97.png[width="150", align="left"] +{empty}[https://github.com/muhd97[github]] [<>] Role: Developer + -Responsibilities: UI +Responsibilities: Data -''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 81be279ef6d..2bac0c4c8f3 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -2,6 +2,12 @@ :site-section: ContactUs :stylesDir: stylesheets -* *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level3/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. +* *Bug reports, Suggestions* : Post in our https://github.com/AY1920S2-CS2103T-W12-4/main/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. * *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] -* *Email us* : You can also reach us at `damith [at] comp.nus.edu.sg` +* *Email us* : You can also reach us at: +** Adarsh: `adarsh@u.nus.edu` +** Kevin: `kevinswk@u.nus.edu` +** Mingsi: `e0316059@u.nus.edu` +** Sharadh: `r.sharadh@u.nus.edu` +** Zain: `zain@u.nus.edu` + diff --git a/docs/DevOps.adoc b/docs/DevOps.adoc index 2aa5a6bc0c1..60ec7e96693 100644 --- a/docs/DevOps.adoc +++ b/docs/DevOps.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Dev Ops += CookBuddy - Dev Ops :site-section: DeveloperGuide :toc: :toc-title: @@ -12,7 +12,7 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S2-CS2103T-W12-4/main/tree/master == Build Automation @@ -41,7 +41,7 @@ Here are the steps to create a new release. == Managing Dependencies -A project often depends on third-party libraries. For example, Address Book depends on the https://github.com/FasterXML/jackson[Jackson library] for JSON parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives: +A project often depends on third-party libraries. For example, *CookBuddy* depends on the https://github.com/FasterXML/jackson[Jackson library] for JSON parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives: [loweralpha] . Include those libraries in the repo (this bloats the repo size) diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 3d65905a853..81c42cc3c06 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,20 +1,62 @@ -= AddressBook Level 3 - Developer Guide += CookBuddy Developer Guide :site-section: DeveloperGuide :toc: -:toc-title: -:toc-placement: preamble +:toc-title: Contents +:toc-placement: auto :sectnums: :imagesDir: images :stylesDir: stylesheets :xrefstyle: full +:experimental: ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S2-CS2103T-W12-4/main/tree/master -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` += Overview + +By: `AY1920S2-CS2103T-W12-4`      Since: `Feb 2020`      Licence: `MIT` + +== Introduction [Done by Zain Alam] + +Welcome to CookBuddy! + +For more information about using CookBuddy, consult the <>. + +CookBuddy is an integrated platform fully customized for users who wish to manage their recipes easily and effectively. + +All your recipe information is stored on our simple and clean Graphical User Interface (GUI) with optimization for users who prefer working on a Command Line Interface (CLI). + +If you are looking for a way to easily manage your recipes and have quick fingers, then CookBuddy is definitely for you! + +== About this Document [Done by Zain Alam] + +This document is a Developer Guide written for developers who wish to contribute to or extend our CookBuddy project. +It is technical, and explains the inner workings of CookBuddy and how the different components of our +application work together. + +We have adopted a "top-down" approach in structuring our Developer Guide with first focusing at the high-level architecture of CookBuddy and then delving into the implementation details of each feature that makes up our application. + +Take note of the following symbols and formatting used in this document: + +TIP: This icon denotes useful tips to note of during development. + +NOTE: This icon denotes important details to take note of during development. + +== Overview of Features [Done by Zain Alam] + +This section will provide you a short overview of CookBuddy's features. + +. Manage your recipes easily +.. Include recipe information e.g. ingredients, instructions, calories, difficulty, rating, serving, tags etc. + +. Various icons to find information easily and quickly +.. Various icons display short information such as difficulty, serving size, tags etc of a recipe. + +. Data is saved onto your disk automatically +.. Any changes made will be saved onto your device so you dont have to worry about the data being lost. == Setting up @@ -26,15 +68,17 @@ Refer to the guide <>. === Architecture .Architecture Diagram -image::ArchitectureDiagram.png[] +image::ArchitectureDiagram.svg[] -The *_Architecture Diagram_* given above explains the high-level design of the App. Given below is a quick overview of each component. +The *_Architecture Diagram_* given above explains the high-level design of CookBuddy. +A quick overview of each component is given below. [TIP] The `.puml` files used to create diagrams in this document can be found in the link:{repoURL}/docs/diagrams/[diagrams] folder. -Refer to the <> to learn how to create and edit diagrams. +Refer to the <> to learn how to create and edit the UML diagrams. -`Main` has two classes called link:{repoURL}/src/main/java/seedu/address/Main.java[`Main`] and link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp`]. It is responsible for, +`Main` has two classes called link:{repoURL}/src/main/java/cookbuddy/Main.java[`Main`] and +link:{repoURL}/src/main/java/cookbuddy/MainApp.java[`MainApp`]. It is responsible for, * At app launch: Initializes the components in the correct sequence, and connects them up with each other. * At shut down: Shuts down the components and invokes cleanup method where necessary. @@ -42,32 +86,27 @@ Refer to the <> to learn how to create and <> represents a collection of classes used by multiple other components. The following class plays an important role at the architecture level: -* `LogsCenter` : Used by many classes to write log messages to the App's log file. +* `LogsCenter` : Used by many classes to write log messages to CookBuddy's log file. -The rest of the App consists of four components. +The rest of CookBuddy consists of four components. -* <>: The UI of the App. +* <>: The UI of CookBuddy. * <>: The command executor. -* <>: Holds the data of the App in-memory. -* <>: Reads data from, and writes data to, the hard disk. +* <>: Holds the data of CookBuddy in-memory. +* <>: Reads data from and writes data to the hard disk. Each of the four components * Defines its _API_ in an `interface` with the same name as the Component. * Exposes its functionality using a `{Component Name}Manager` class. -For example, the `Logic` component (see the class diagram given below) defines it's API in the `Logic.java` interface and exposes its functionality using the `LogicManager.java` class. - -.Class Diagram of the Logic Component -image::LogicClassDiagram.png[] - [discrete] ==== How the architecture components interact with each other The _Sequence Diagram_ below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. .Component interactions for `delete 1` command -image::ArchitectureSequenceDiagram.png[] +image::ArchitectureSequenceDiagram.svg[align="center"] The sections below give more details of each component. @@ -75,39 +114,49 @@ The sections below give more details of each component. === UI component .Structure of the UI Component -image::UiClassDiagram.png[] +image::UiClassDiagram.svg[] -*API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] +*API* : link:{repoURL}/src/main/java/cookbuddy/ui/Ui.java[`Ui.java`] -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. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `RecipeListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. -The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] +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 link:{repoURL}/src/main/java/cookbuddy/ui/MainWindow.java[`MainWindow`] is specified in +link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] The `UI` component, * Executes user commands using the `Logic` component. * Listens for changes to `Model` data so that the UI can be updated with the modified data. +[NOTE] +==== +The `UI` component uses the JMetro library to apply a theme to the default JavaFX interface. The resulting product presents a neat, Windows 10-style UI to the user, that employs https://www.microsoft.com/design/fluent/#/[Microsoft's Fluent Design patterns]. + +For more information on JMetro, refer to the https://pixelduke.com/java-javafx-theme-jmetro/[JMetro home page]. +==== + [[Design-Logic]] === Logic component [[fig-LogicClassDiagram]] .Structure of the Logic Component -image::LogicClassDiagram.png[] +image::LogicClassDiagram.svg[align="center"] *API* : -link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] +link:{repoURL}/src/main/java/cookbuddy/logic/Logic.java[`Logic.java`] -. `Logic` uses the `AddressBookParser` class to parse the user command. +. `Logic` uses the `RecipeBookParser` class to parse the user command. . This results in a `Command` object which is executed by the `LogicManager`. -. The command execution can affect the `Model` (e.g. adding a person). +. The command execution can affect the `Model` (e.g. adding a recipe). . The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. . In addition, the `CommandResult` object can also instruct the `Ui` to perform certain actions, such as displaying help to the user. Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. .Interactions Inside the Logic Component for the `delete 1` Command -image::DeleteSequenceDiagram.png[] +image::DeleteSequenceDiagram.svg[] NOTE: The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. @@ -115,132 +164,485 @@ NOTE: The lifeline for `DeleteCommandParser` should end at the destroy marker (X === Model component .Structure of the Model Component -image::ModelClassDiagram.png[] +image::ModelClassDiagram.svg[align="center"] -*API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] +*API* : link:{repoURL}/src/main/java/cookbuddy/model/Model.java[`Model.java`] The `Model`, * stores a `UserPref` object that represents the user's preferences. -* stores the Address Book data. -* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the Recipe Book data. +* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * does not depend on any of the other three components. [NOTE] -As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` can reference. This would allow `Address Book` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object. An example of how such a model may look like is given below. + +As a more OOP model, we can store a `Tag` list in `Recipe Book`, which `Recipe` can reference. This would allow `Recipe Book` to only require one `Tag` object per unique `Tag`, instead of each `Recipe` needing their own `Tag` object. An example of how such a model may look like is given below. + + -image:BetterModelClassDiagram.png[] +image:BetterModelClassDiagram.svg[] + +==== The `attribute` package +[[attrib-package]] +.Structure of the `attribute` package, defining each `Recipe` 's key attributes +image::AttributeClassDiagram.svg[align="center"] +`attribute` defines common attributes for each recipe, such as time taken to cook, serving size, an image of the recipe, and so on. [[Design-Storage]] === Storage component .Structure of the Storage Component -image::StorageClassDiagram.png[] +image::StorageClassDiagram.svg[align="center"] -*API* : link:{repoURL}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] +*API* : link:{repoURL}/src/main/java/cookbuddy/storage/Storage.java[`Storage.java`] -The `Storage` component, +The `Storage` component manages the recipe data, the user configuration, preferences, and the image data. To be specific, it: -* can save `UserPref` objects in json format and read it back. -* can save the Address Book data in json format and read it back. +* reads and writes `UserPref` objects to and from disk, as `.json` format; +* serialises, reads and writes recipe data to and from disk, _also_ as `.json` format; +* passes on the file path for the user-entered image into methods in the `ImageUtil` class. More details are given in <>. [[Design-Commons]] === Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `cookbuddy.commons` package; the three over-arching sub-packages are `core`, `exceptions`, and `util`. + +==== `core` +This package defines classes for user configuration, GUI settings, and even a version number. + +==== `exceptions` +This package defines exceptions thrown by CookBuddy when it encounters an error state. + +==== `util` +This package defines utility classes for certain operations, like file I/O, argument validation, and image processing. + + + -== Implementation +== Implementation details and Possible Enhancements This section describes some noteworthy details on how certain features are implemented. +Some possible future components are briefly covered, and these may be released in `v2.0`. -// tag::undoredo[] -=== [Proposed] Undo/Redo feature -==== Proposed Implementation -The 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: +=== Counting recipes [Done by Zain Alam] -* `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. +We allow users to count the total number of recipes stored in CookBuddy. This section shows how we handle this request from the user. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +==== Implementation -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +We store every single `Recipe` added by the user into an `ObservableList`, which is a list object in `UniqueRecipeList`. We used an `ObservableList` to easily reflect changes to the list by any other component of CookBuddy using the list. -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. +The `count` command was implemented as a `CountCommand` in the `bookbuddy/logic/commands` package. -image::UndoRedoState0.png[] +The `count` has the following input format: `count` -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. +The following sequence diagram shows how the count operation works: -image::UndoRedoState1.png[] +:figure-caption: Figure +.Sequence diagram of how `count` command is processed. -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`. +image::count-command/CountSequenceDiagram.svg[align="center"] -image::UndoRedoState2.png[] +An incorrect syntax will cause a `ParseException` to be thrown by the parser. [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`. +Incorrect user input will display Unknown command message. -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. +We will now demonstrate how a `count` command works in `CookBuddy`: -image::UndoRedoState3.png[] +Step 1. The user executes the command **count** to count the total number of recipes stored in CookBuddy. -[NOTE] -If the `currentStatePointer` is at index 0, pointing to the initial address book state, then there are no previous address book 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. +Step 2. The input is now checked and an attempt to parse the parameter occurs. The `CountCommand#execute(Model model)` method is executed. -The following sequence diagram shows how the undo operation works: +Step 3. The method `Model#count()` will then be called to calculate the total number of recipes stored in CookBuddy. -image::UndoSequenceDiagram.png[] +Step 4. If successful, a success message will be generated by `CommandResult` and it will be returned with the generated +success message. Otherwise, an error message is thrown as `ParseException`. -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. +Since the user, input in this case, is valid, +the `count` command is successfully executed and the total number of recipes currently stored in CookBuddy is displayed. -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. +The following activity diagram summarizes what happens when the user executes `count` command to count the total number of recipes: -[NOTE] -If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone address book 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. +:figure-caption: Figure +.Activity diagram of executing the `count`command. +image::count-command/CountActivityDiagram.svg[align="center",650] + +=== Image Management [Done by Sharadh Rajaraman] +CookBuddy allows users to add images to their recipes, which are then saved into the `data/images` folder created by CookBuddy (at least, using the default settings). This section elaborates on implementation. + +The two key classes allowing image and photo management are `ImageUtil` and `Photograph`; the former is a utility class written in the https://en.wikipedia.org/wiki/Singleton_pattern[singleton pattern]; the latter is the attribute that each `Recipe` directly contains, as <> details. + +==== From User Command to Image On Screen + +[[img-seq]] +.Sequence Diagram of image retrieval from disk +image::PhotoSequenceDiagram.svg[align="center] + +The above sequence diagram details how a user-entered command is translated to an image file as displayed on screen. Some initial details are omitted, such as the calls to `RecipeBookParser#parseCommand()` (which have already been demonstrated in <>). + +The steps taken are also described step-by-step in the activity diagram below: + +.Activity diagram of <> +image::PhotoReadActivityDiagram.svg[align="center"] + +==== Saving Images Into the Data Folder +When saving images, there were a few considerations that needed to be taken into account: + +* The image on disk *must* contain the recipe name, so as to be reasonably understandable; + +* The image on disk must be stored _losslessly_, so that repeated read-write cycles do not deplete the quality; + +* If an image already exists on disk, then read/write cycles must not be wasted in overwriting an image with the same data; + +* Even if recipes have the same name, the image file names must be distinct, and yet always resolve to the same. + +Therefore, a hashcode is appended to each image file name, and the resulting data is saved to disk as a `.png` image, which is lossless. `jpeg` formats would require lossy compression at each save, which would progressively degrade image quality. + +This entire process is also demonstrated in the activity diagram below: + +.Activity diagram representing image save process +image::PhotoSaveActivityDiagram.svg[align="center"] + +[[Img-considerations]] +==== Design Considerations +`ImageUtil` is implemented as a singleton class. In other words, its constructor is declared `private`, and the object can only be retrieved by the `public static` factory method, `ImageUtil.imageUtil()`. Given the class defines _several_ constants using methods, we believed this was the most straightforward direction possible. + +`ImageUtil` also declares `PLACEHOLDER_IMAGE` as several static constant types: a `BufferedImage`, an `InputStream`, and even as a `Path`. The _actual_ image is bundled with the `JAR` file, which can be explored at will using an extractor tool like `7zip`. + +These constants are loaded when the Photograph class is first called, thus adhering to Java's Just-In-Time (JIT) principle. + +Furthermore, the initial `ImageUtil` written as an ordinary static utility class led to the JVM throwing `ExceptionInInitializerError` when the built `.jar` was run. There were no issues running this from the IDE; hence the singleton pattern. + + +==== Possible Improvements +As it is, image processing spans _several_ classes: `FileUtil`, `ImageUtil`, `Photograph`. We would like to simplify this. Furthermore, _saving_ image data requires returning a file path through several methods, which have little relation to one another. + + +// tag::FindCommandImpl[] +=== Finding recipes [Done by Kevin] + +The following section describes how the `find` command is implemented as well as design considerations that were taken into account during its implementation. +Some possible future improvements are also suggested to improve the functionality of the `find` command. + +The `find` command is implemented in the _FindCommand_ class. + +The following activity diagram shows the possible paths *CookBuddy* can take when a `find` command is run. + +.Activity diagram of finding recipes +image::FindCommandActivityDiagram.svg[align="center] + +==== Implementation + +. When entering the `find` command, the user will specify one attribute to search within as well as the search terms. + + Possible attributes to search within are _name_ (`n/`), _ingredients_ (`ing/`) and _instructions_ (`ins/`). + +. _FindCommandParser_ ensures that only one attribute is specified and returns a _FindCommand_ with the relevant _ContainsKeywordPredicate_ class. + +The following sequence diagram summarizes the execution of the `find` command + +.Sequence diagram for the execution of a `find` command +image::FindCommandSequenceDiagram.svg[align="center"] + +==== Design Considerations + +As the _FindCommand_ class only takes in a single _ContainsKeywordsPredicate_ object, +CookBuddy can only search within a single attribute at a time. + +==== Possible improvements + +As it is currently implemented, the `find` command only accepts finding via one attribute at a time. +A possible future improvement would enable it to search for recipes using multiple attributes. +This would greatly enhance the usefulness of the `find` function in *CookBuddy*. + +For example, running `find n/Ham ing/toast` will make *CookBuddy* search for recipes with _Ham_ in its name, or _toast_ in its ingredients. + +// end::FindCommandImpl[] + + +// tag::FavCommandImpl[] +=== Favouriting recipes [Adarsh] + +The following section describes how the `fav` command is implemented as well as design considerations that were taken into account during its implementation. +Some possible future improvements are also suggested to improve the functionality of the `fav` command. + +The `fav` command is implemented in the _FavCommand_ class. + +The following activity diagram shows the possible paths *CookBuddy* can take when a `fav` command is run. -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. +.Activity diagram of the `fav` command +image::FavActivityDiagram.svg[align="center] -image::UndoRedoState4.png[] +==== Implementation -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. We designed it this way because it no longer makes sense to redo the `add n/David ...` command. This is the behavior that most modern desktop applications follow. +. When entering the `fav` command, the user will specify the index of the recipe to be favourited. -image::UndoRedoState5.png[] +. _FavCommandParser_ ensures that index specified is valid and returns a _FavCommand_. -The following activity diagram summarizes what happens when a user executes a new command: +The following sequence diagram summarizes the execution of the `fav` command -image::CommitActivityDiagram.png[] +.Sequence diagram for the execution of a `fav` command +image::FavSequenceDiagram.svg[align="center"] ==== Design Considerations -===== Aspect: How undo & redo executes +===== Aspect: How many indexes should be taken in? + +* Alternative 1 (Chosen): Only 1 index to be specified per use of the command + +** Pros: Less error-prone +** Cons: Less efficient + +* Alternative 2: Multiple indexes can be specified per use of the command + +** Pros: More efficient +** Cons: More error prone + + + + +==== Possible improvements + +In the current implementation, you are able to favourite recipes, even if they have already been favourited. +A possible improvement would be to notify users if the recipe they are trying to favourite has already been favourited. +This would greatly enhance the usefulness of the `fav` function in *CookBuddy*. + +// end::FavCommandImpl[] + + +// tag::UnFavCommandImpl[] +=== Un-favouriting recipes [Adarsh] + +The following section describes how the `unfav` command is implemented as well as design considerations that were taken into account during its implementation. +Some possible future improvements are also suggested to improve the functionality of the `unfav` command. + +The `unfav` command is implemented in the _UnFavCommand_ class. + +The following activity diagram shows the possible paths *CookBuddy* can take when a `unfav` command is run. + +.Activity diagram of the `unfav` command +image::UnfavActivityDiagram.svg[align="center] + +==== Implementation + +. When entering the `unfav` command, the user will specify the index of the recipe to be un-favourited. + +. _UnFavCommandParser_ ensures that index specified is valid and returns a _UnFavCommand_. + +The following sequence diagram summarizes the execution of the `unfav` command + +.Sequence diagram for the execution of a `unfav` command +image::UnfavSequenceDiagram.svg[align="center"] + +==== Design Considerations + +===== Aspect: How many indexes should be taken in? + +* Alternative 1 (Chosen): Only 1 index to be specified per use of the command + +** Pros: Less error-prone +** Cons: Less efficient + +* Alternative 2: Multiple indexes can be specified per use of the command + +** Pros: More efficient +** Cons: More error prone + +==== Possible improvements + +In the current implementation, you are able to un-favourite recipes, even if they are not favourited. +A possible improvement would be to notify users if the recipe they are trying to favourite has not been favourited. +This would greatly enhance the usefulness of the `find` function in *CookBuddy*. + +// end::UnFavCommandImpl[] + + +// tag::DoneCommandImpl[] +=== Marking recipes as done [Adarsh] + +The following section describes how the `done` command is implemented as well as design considerations that were taken into account during its implementation. +Some possible future improvements are also suggested to improve the functionality of the `done` command. + +The `done` command is implemented in the _DoneCommand_ class. + +The following activity diagram shows the possible paths *CookBuddy* can take when a `done` command is run. + +.Activity diagram of the `done` command +image::DoneActivityDiagram.svg[align="center] + +==== Implementation + +. When entering the `done` command, the user will specify the index of the recipe to be mared as done. + +. _DoneCommandParser_ ensures that index specified is valid and returns a _DoneCommand_. + +The following sequence diagram summarizes the execution of the `done` command + +.Sequence diagram for the execution of a `done` command +image::DoneSequenceDiagram.svg[align="center"] + +==== Design Considerations + +===== Aspect: How many indexes should be taken in? + +* Alternative 1 (Chosen): Only 1 index to be specified per use of the command + +** Pros: Less error-prone +** Cons: Less efficient + +* Alternative 2: Multiple indexes can be specified per use of the command + +** Pros: More efficient +** Cons: More error prone + +==== Possible improvements + +In the current implementation, you are able to mark recipes as done, even if they are already marked as done. +A possible improvement would be to notify users if the recipe they are trying to favourite has already been marked as done. +This would greatly enhance the usefulness of the `done` function in *CookBuddy*. + +// end::DoneCommandImpl[] + + +// tag::UndoCommandImpl[] +=== Un-marking recipes as done [Adarsh] + +The following section describes how the `undo` command is implemented as well as design considerations that were taken into account during its implementation. +Some possible future improvements are also suggested to improve the functionality of the `undo` command. + +The `undo` command is implemented in the _UndoCommand_ class. + +The following activity diagram shows the possible paths *CookBuddy* can take when a `undo` command is run. + +.Activity diagram of the `undo` command +image::UndoActivityDiagram.svg[align="center] + +==== Implementation + +. When entering the `undo` command, the user will specify the index of the recipe to be un-favourited. + +. _UndoCommandParser_ ensures that index specified is valid and returns a _UndoCommand_. + +The following sequence diagram summarizes the execution of the `undo` command + +.Sequence diagram for the execution of a `undo` command +image::UndoSequenceDiagram.svg[align="center"] + +==== Design Considerations + +===== Aspect: How many indexes should be taken in? + +* Alternative 1 (Chosen): Only 1 index to be specified per use of the command + +** Pros: Less error-prone +** Cons: Less efficient + +* Alternative 2: Multiple indexes can be specified per use of the command + +** Pros: More efficient +** Cons: More error prone + +==== Possible improvements + +In the current implementation, you are able to mark recipes as not attempted, even if they have not been marked as attempted. +A possible improvement would be to notify users if the recipe they are trying to unmark as done has not been attempted. +This would greatly enhance the usefulness of the `undo` function in *CookBuddy*. + +// end::UndoCommandImpl[] + + +// tag::ViewCommandImpl[] +=== Viewing recipes [Adarsh] + +The following section describes how the `view` command is implemented as well as design considerations that were taken into account during its implementation. +Some possible future improvements are also suggested to improve the functionality of the `view` command. -* **Alternative 1 (current choice):** Saves the entire address book. -** Pros: Easy to implement. -** Cons: May have performance issues in terms of memory usage. -* **Alternative 2:** Individual command knows how to undo/redo by itself. -** Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). -** Cons: We must ensure that the implementation of each individual command are correct. +The `view` command is implemented in the _ViewCommand_ class. -===== Aspect: Data structure to support the undo/redo commands +The following activity diagram shows the possible paths *CookBuddy* can take when a `view` command is run. -* **Alternative 1 (current choice):** Use a list to store the history of address book states. -** Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project. -** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedAddressBook`. -* **Alternative 2:** Use `HistoryManager` for undo/redo -** Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase. -** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two different things. -// end::undoredo[] +.Activity diagram of the `view` command +image::ViewActivityDiagram.svg[align="center] -// tag::dataencryption[] -=== [Proposed] Data Encryption +==== Implementation + +. When entering the `view` command, the user will specify the index of the recipe that they wish to view. + +. _ViewCommandParser_ ensures that index specified is valid and returns a _ViewCommand_. + +The following sequence diagram summarizes the execution of the `view` command + +.Sequence diagram for the execution of a `view` command +image::ViewSequenceDiagram.svg[align="center"] + +==== Design Considerations + +===== Aspect: How should the user specify the recipe they want to view + +* Alternative 1 (Chosen): Using the index of the recipe + +** Pros: Easily identified from the GUI +** Cons: - + +* Alternative 2: Using the name of the recipe + +** Pros: Users may know the name of the recipe better than the index +** Cons: Less efficient + +==== Possible improvements + +In the current implementation, the `view` command only takes in the index of the recipe. +A possible improvement would be to allow users to input the recipe name they want viewed. +That way, users can input either the recipe name or index. + +// end::ViewCommandImpl[] + + +// tag::TimeCommandImpl[] +=== Assigning a time to a recipe [Adarsh] + +The following section describes how the `time` command is implemented as well as design considerations that were taken into account during its implementation. +Some possible future improvements are also suggested to improve the functionality of the `time` command. + +The `time` command is implemented in the _TimeCommand_ class. + +The following activity diagram shows the possible paths *CookBuddy* can take when a `time` command is run. + +.Activity diagram of the `time` command +image::TimeActivityDiagram.svg[align="center] + +==== Implementation + +. When entering the `time` command, the user will input the index of the recipe they wish to assign a time, as well as the time they wish to assign to the recipe. The time is to be in the following format (hh:MM:ss). Minutes and seconds are optional, and would be set to 0 if no values are provided for them. + +. _TimeCommandParser_ ensures that index specified is valid and returns a _TimeCommand_. + +The following sequence diagram summarizes the execution of the `time` command + +.Sequence diagram for the execution of a `time` command +image::TimeSequenceDiagram.svg[align="center"] + +==== Design Considerations + +===== Aspect: Should the minutes and seconds be mandatory inputs + +* Alternative 1 (Chosen): No, they can be optional inputs + +** Pros: More efficient +** Cons: - + +* Alternative 2: Yes, they should be mandatory inputs + +** Pros: - +** Cons: Much less efficient + +==== Possible improvements + +In the current implementation, we are parsing the hours, minutes and seconds as integers. +A possible improvement would be to use the java.util.time class. + +// end::TimeCommandImpl[] -_{Explain here how the data encryption feature will be implemented}_ -// end::dataencryption[] === Logging @@ -260,7 +662,23 @@ We are using `java.util.logging` package for logging. The `LogsCenter` class is [[Implementation-Configuration]] === Configuration -Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: `config.json`). +Certain properties of the application may be controlled (e.g user prefs file location, logging level) through the configuration file (default: `config.json`). + +[[Implementation-Future]] +=== Features coming in `v2.0` +We have some exciting features in line for `v2.0`, and some of these include: + +* Converting between units: American users of our application might be more comfortable with pounds, quarts, gallons and such. Therefore, we aim to implement a seamless conversion between US customary and metric units, with a one-command (or click) way to switch between the two. + +* We also note that users may prefer some advanced UNIX-style compressed syntax; hence, we are exploring using the https://picocli.info/[PicoCLI] library to implement _both_ the current Windows-style slash-based syntax, as well as UNIX-style dash-based syntax. This is a _single_ class that can either be imported as a dependency, or directly included as source. ++ +We expect that this change would also drastically decrease code length and improve stability and testability, given the current implementation spans not just several _classes_, but two entire _packages_: `cookbuddy.logic.parser`, and `cookbuddy.logic.command`. As it is now, we have needless object-orientation for the sake of doing so, and we believe in simplicity as much as possible. + +* We understand users who cook love to share their creations with friends and family; therefore, we intend to use social media APIs from Facebook, Google, Twitter, Reddit, Instagram, and Snapchat, to allow users to share their recipes to the world. + +* Users might not be comfortable with keeping their computers near the stove, hence, we plan to offer two solutions in `v2.0`: +** We plan to release mobile apps for the two major platforms, Android and iOS. +** For users more comfortable with paper, we plan to allow _printing_ of recipes to PDF, and directly to printers. This engine is intended to be powered by `LaTeX`. == Documentation @@ -275,151 +693,492 @@ Refer to the guide <>. Refer to the guide <>. [appendix] -== Product Scope +== Product Scope [Done by Zain Alam & Sharadh Rajaraman] + +*Target user profile*: [Done by Sharadh Rajaraman] + +* cooks for oneself on a nearly daily basis, and hence: + - needs to manage many recipes + - needs to have a clean interface to view and read recipes + - experiments with dishes -*Target user profile*: +* prefers desktop apps over other types -* has a need to manage a significant number of contacts -* prefer desktop apps over other types * can type fast + * prefers typing over mouse input -* is reasonably comfortable using CLI apps -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +* is reasonably familiar with the command-line -[appendix] -== User Stories +* requires a straightforward means to catalogue and codify dishes and meals without using spreadsheets -Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` +*Value proposition*: [Done by Zain Alam] -[width="59%",cols="22%,<23%,<25%,<30%",options="header",] -|======================================================================= -|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 +* Store, retrieve, manage and display recipes faster than navigating through websites and bookmarks, with command-line input, but GUI responses. -|`* * *` |user |add a new person | +* Present a unified interface for recipe management. -|`* * *` |user |delete a person |remove entries that I no longer need +* When managing recipes: + - allow easier and faster retrieval with attributes like tags, time, difficulty, etc; -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list + - present a straightforward interface to edit, duplicate and combine recipes into meals -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +* Overall increase in productivity. -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= -_{More to be added}_ +[appendix] +// tag::UserStories[] +== User Stories [Done by Sharadh Rajaraman and Adarsh Chugani] + +Priorities: *High* (must have), *Medium* (nice to have), *Low* (unlikely to have) + +[width="100%",cols="10%, 30%, 30%, 30%", options="header",] +|=== +| Priority | As a/an … | I want to … | So that… +| *High* | Regular user | add a recipe | I can keep track of the recipe +| *High* | Regular user | delete a recipe | I can stop keeping track of the recipe +| *High* | Regular user | list out all the recipes I have | I can easily see what recipies I have +| *High* | Regular user | view the recipe | I can use the recipe +| *High* | More experienced user | duplicate a recipe | I can modify a copy and keep the original +| *High* | User who is inexperienced with software | use a helper command | I can see all the commands and how to use them +| *High* | Regular user | add instructions for the recipe | I know how to cook the dish +| *High* | Regular user | add ingredient to recipe | I know how much ingredients to use +| *High* | Health-focused user | track the amount of calories a dish has | I know how healthy a dish is +| *High* | Regular user | add time it takes to prepare / cook recipe | I know how long it takes to cook the recipe +| *High* | Organized user | tag recipes based on meal time (breakfast/lunch/dinner) | I can easily refer to them +| *High* | User who likes experimenting | modify a recipe | I can change the components of the recipe +| *High* | Regular user | add a serving size of a dish | I know the serving size of the recipe +| *High* | Time-strapped user | see the preparation and cooking time for each recipe | I can plan my schedule around the time needed +| *High* | Health-focused user | search for a dish based on how many calories i want to consume | I can eat healthily +| *High* | User who is new to cooking | tag recipes based on difficulty (beginner/intermediate/master) | I can check if I am skilled enough to cook the dish +| *High* | User with many recipes | tag recipes based on their cuisine (western, chinese, indian etc) | I can find them easily +| *Medium* | User who wants to be efficient | favourite recipes/dishes | I can easily refer back to them +| *Medium* | User who wants to get rid of ingredients | search for dishes based on ingredients | I can use up the ingredients that I want to get rid of +| *Medium* | User with many recipes | search for recipes based on a word in the dish name | I can find it easily +| *Medium* | Organized user | mark recipe as successfully done | I can keep track of the recipes I have successfully attempted +| *Medium* | Inexperienced user | view the recipe in a GUI | I have more visual feedback to work with +| *Medium* | Inexperienced user | view an image of the final dish | I know what dish I am cooking +| *Medium* | Organized user | have a counter of total recipes in the book | I can know how many recipes I have +| *Medium* | User who likes experimenting | give me a random recipe that i have added | I can challenge myself to cook what has been given +| *Medium* | Regular user | give a rating for the dish | I can tag, search for and sort dishes based on my rating of the dish +| *Medium* | User with a limited budget | find recipes within my budget | I do not overspend +| *Medium* | User with allergies | tag the dish as dangerous for allergies | I can avoid cooking the dish +| *Medium* | User who not experienced | highlight instructions in the recipe | I can follow the recipe more easily +| *Medium* | Organized user | sort my recipes based on criteria (tags) | I can choose what order to view them +| *Medium* | Regular user | add ingredient prices | I can tabulate the total cost of cooking dishes +| *Low* | User with a limited budget | view the price of a specific ingredient | I know how much a ingredient costs +| *Low* | User with many friends | import and combine my friend's recipes from a file (.txt perhaps) | I can have access to their recipies +| *Low* | User who enjoys challenging themselves | suggest dish to attempt based on my previous successful attempts | I can become more skillful +| *Low* | Regular user | choose to only see the basic information for the recipe | I can easily skim through the instructions and ingredients +| *Low* | User on a budget | check the total price of the dish | I can check if it is within my budget +| *Low* | User cooking for a group / occasion | scale up/down the recipe | I can prepare food for different group sizes +| *Low* | User cooking for a group / occasion | find out how much of each ingredient i need | I can get the ingredients at one go +| *Low* | Health-focused user | add nutrition facts | I can see how much sugar, salt, fat etc is in the dish prepared +| *Low* | User who is more familiar with the metric system | Convert between metric and imperial sizes. | I can use the tools I have without needing to convert elsewhere +| *Low* | User who usually prepares multiple dishes as sets | group dishes into sets | I can be more organised when cooking +| *Low* | User who is experienced with the software | use shorthand commands | I can navigate the software more efficiently +| *Low* | User who wants to challenge myself | have a timer/stopwatch | I can time myself when I cook dishes and have a "best time" feature +| *Low* | User who doesn't like screens and prefers paper | print recipes as pdf/paper | I can refer to it more easily +| *Low* | User who likes sharing my cooking | post my recipes and dishes on social media | I can share recipes and images for others to use +| *Low* | User who appreciates efficiency | add a recipe directly from online | I can be efficient +|=== + +// end::UserStories[] [appendix] -== Use Cases +== Use Cases [Done by Sharadh Rajaraman] -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +(For all use cases below, the *System* is `CookBuddy` and the *Actor* is the `User`, unless specified otherwise) [discrete] -=== Use case: Delete person +=== Use case: List recipes *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 recipes +2. `CookBuddy` displays the list of recipes + Use case ends. *Extensions* +[none] +* 1a. The name cannot be found, or the index is invalid. ++ +[none] +** 1a1. `CookBuddy` throws an error message. ++ +Use case resumes at step 1. + [none] * 2a. The list is empty. + +[none] +** 2a1. `CookBuddy` displays a message stating the list is empty ++ +Use case ends. + + + +[discrete] +=== Use case: Delete recipe + +*MSS* + +1. `User` requests to delete a specific _recipe_ by specifying its index +2. CookBuddy deletes the recipe ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The name cannot be found, or the index is invalid. ++ +[none] +** 1a1. `CookBuddy` throws an error message. ++ +Use case ends. + + +[discrete] +=== Use case: Modify recipe + +*MSS* + +1. `User` requests to modify a recipe +2. `CookBuddy` edits attributes of the recipe, and asks for user confirmation +3. `User` confirms the edit ++ Use case ends. -* 3a. The given index is invalid. +*Extensions* + +[none] +* 1a. `User` does not provide new attributes. +[none] +** `CookBuddy` throws an error message. ++ +Use case resumes at step 1. +* 2a. `User` does not confirm. + [none] -** 3a1. AddressBook shows an error message. +** 2a1. `CookBuddy` does not save the edit + -Use case resumes at step 2. +Use case ends. + -_{More to be added}_ [appendix] -== Non Functional Requirements +== Non Functional Requirements [Done by Zain Alam] . Should work on any <> as long as it has Java `11` or above installed. -. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +. Should be able to hold up to 1000 recipes without noticeable sluggishness in performance for typical usage. . 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. +. Commands should be <> commands as opposed to <>. +. CookBuddy should be able to function without internet access. +. A user should be able to familiarise herself with less than 30 minutes of usage. -_{More to be added}_ [appendix] -== Glossary +== Glossary [Done by Zain Alam] + +[[attributes]] Attributes:: +The information of a recipe. For example, calories, ingredients or instructions etc. [[mainstream-os]] Mainstream OS:: -Windows, Linux, Unix, OS-X +Windows, macOS, Linux, UNIX -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others +[[multi-level-c]] Multi-level Commands:: +Commands that require multiple lines of user input for execution. + +[[one-shot-c]] One-shot Commands:: +Commands that are executed using only a single line of user input. + +[[recipe]] Recipe:: +A list of ingredients followed by a list of instructions, detailing how to prepare a dish. + +[[tag]] Tag:: +A (possibly custom) text marker that users can use to organise their recipes; examples include `vegetarian`, `spicy`, `Indian`. Tags can themselves be organised into groups, such as `cuisines`, `diet`, `ingredients`, `mealtime`, etc. [appendix] -== Product Survey +== Product Survey [Done by Zain Alam] -*Product Name* +*CookBuddy* -Author: ... +Author: Zain Alam Pros: -* ... -* ... +** Functionality +* Ease of recipe management +* Tracks calories, rating and diffculty + +** Non-funtional requirements +* Well-designed GUI +* Cross platform Cons: -* ... -* ... +** Functionality +* Unable to find a recipe with more than one parameters at a time +* Unable to pin recipes when working with multiple meals +* Unable to translate a recipe from one language to another language + +** Non-functional requirements +* slightly GUI-dependent, some buttons need to be clicked and screens traversed to perform a task + [appendix] -== Instructions for Manual Testing +// tag::ManualTesting[] +== Instructions for Manual Testing [Done by Kevin] Given below are instructions to test the app manually. [NOTE] -These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing. +These instructions only provide a starting point for testers to work on; testers are advised to do more _exploratory_ testing. === Launch and Shutdown -. Initial launch +. Launching *CookBuddy* -.. Download the jar file and copy into an empty folder -.. Double-click the jar file + - Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. +.. Ensure you are using *Java 11* by opening a Command Prompt / terminal and run `java -version`. +.. Download the latest *CookBuddy* jar file https://github.com/AY1920S2-CS2103T-W12-4/main/releases[here] and copy it into an empty folder +.. Launch Command Prompt / a terminal, navigate to the folder *CookBuddy* is in and enter `java -jar CookBuddy.jar`. Do not double-click CookBuddy.jar + + Expected: Shows *CookBuddy's* GUI with a set of sample recipes. The window size may not be optimum. . Saving window preferences -.. Resize the window to an optimum size. Move the window to a different location. Close the window. -.. Re-launch the app by double-clicking the jar file. + +.. Resize *CookBuddy's* window to an optimum size. Move the window to a different location on the screen. Close the window. +.. Re-launch *CookBuddy* by entering `java -jar CookBuddy.jar` in a Command Prompt / terminal. + Expected: The most recent window size and location is retained. -_{ more test cases ... }_ +. Exiting *CookBuddy* + +.. On Windows, click the _Close Window_ button btn:[X] on the top-right corner of *CookBuddy's* GUI +.. On Mac, click the _Close Window_ button btn:[X] on the top-left corner of *CookBuddy's* GUI +.. Type `exit` in *CookBuddy's* command box and press kbd:[Enter] ++ +Expected: *CookBuddy* will shut down. + +=== Adding a recipe + +. Add a recipe with all mandatory fields present. Name (n/), Ingredients (ing/) and Instructions (ins/) are mandatory fields. + +.. Prerequisites: The recipe to be added is not present in the recipe book. +.. Test case: `new n/Eggs on Toast ing/bread, 2 slices; egg, 1 ins/toast the 2 slices of bread; scramble the eggs; + put eggs on toasted bread; serve` + + Expected: The _Eggs on Toast_ recipe is added to the recipe list. Details of the newly added recipe is shown in the result pane. + +. Add a recipe with one mandatory field missing, Instructions in this case. + +.. Test case: `new n/Eggs on Toast ing/bread, 2 slices; egg, 1` + + Expected: No recipe is added. An _"Invalid command format"_ error message is shown in the result pane. + +. Add a recipe with a missing ingredient quantity (egg is missing its quantity) + +.. Test Case: `new n/Eggs on Toast ing/bread, 2 slices; egg ins/toast the 2 slices of bread; scramble the eggs; + put eggs on toasted bread; serve` + + Expected: No recipe is added. Error message _No quantity has been provided for one or more ingredients!_ is shown in the result pane. + +=== Modifying a recipe + +[NOTE] +*CookBuddy* should contain at least one recipe. +If no recipe exists, delete the _data_ folder and re-launch *CookBuddy*. +The recipe book should contain two recipes, _Ham Sandwich_ & _Idiot Sandwich_. + +[TIP] +The Modify command allows changing multiple attributes in one command. +For example, `modify 1 n/Rice cal/250` updates both recipe 1's name and calories. + +. Modifying a recipe's name + +.. Test Case: `modify 1 n/Chicken Rice` + + Expected: The first recipe's name is updated to _Chicken Rice_. +.. Test Case: `modify 1 n/` + + Expected: The first recipe's name is not updated. Error details are shown in in result pane. +.. Test Case: `modify 1 n/!@#abc` + + Expected: The first recipe's name is not updated. Error details are shown in in result pane. + +. Modifying a recipe's ingredients + +.. Test Case: `modify 1 ing/ing1, qty1; ing2, qty2` + + Expected: The first recipe's original ingredients should be overwritten with ing1 & ing2. +.. Test Case: `modify 1 ing/ing1, ; ing2, qty2` + + Expected: The first recipe's ingredients are not updated due to ing1 missing its quantity. +.. Test Case: `modify 1 ing/, qty1; ing2, qty2` + + Expected: The first recipe's ingredients are not updated due to ing1 missing its name. + +. Modifying a recipe's instructions + +.. Test Case: `modify 1 ins/ins1; ins2` + + Expected: The first recipe's original instructions should be overwritten with ins1 & ins2. +.. Test Case: `modify 1 ins/` + + Expected: The first recipe's instructions should not be updated. Error details are shown in in result pane. + +. Modifying a recipe's calories + +.. Test Case: `modify 1 cal/2000` + + Expected: The first recipe's calories should be updated to 2000 kCal. +.. Test Case: `modify 1 cal/abc` + + Expected: The first recipe's calories is not updated. Error details are shown in in result pane. -=== Deleting a person +. Modifying a recipe's serving size -. Deleting a person while all persons are listed +.. Test Case: `modify 1 s/3` + + Expected: The first recipe's serving size should be updated to 3. +.. Test Case: `modify 1 s/abc` + + Expected: The first recipe's serving size is not updated. Error details are shown in in result pane. -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +. Modifying a recipe's difficulty + +.. Test Case: `modify 1 d/4` + + Expected: The first recipe's difficulty should be updated to 4 on a scale of 1 to 5. +.. Test Case: `modify 1 d/6` + + Expected: The first recipe's difficulty is not updated. Error details are shown in in result pane. +.. Test Case: `modify 1 d/abc` + + Expected: The first recipe's difficulty is not updated. Error details are shown in in result pane. + +. Modifying a recipe's rating + +.. Test Case: `modify 1 r/5` + + Expected: The first recipe's rating should be updated to 5 stars. +.. Test Case: `modify 1 r/8` + + Expected: The first recipe's rating is not updated. Error details are shown in in result pane. +.. Test Case: `modify 1 r/abc` + + Expected: The first recipe's rating is not updated. Error details are shown in in result pane. + +. Modifying a recipe's tags + +.. Test Case: `modify 1 t/breakfast` + + Expected: The first recipe's tags should be updated to contain *one* tag, _breakfast_. +.. Test Case: `modify 1 t/breakfast, lunch` + + Expected: The first recipe's tags should be updated to contain *two* tags, _breakfast_ & _lunch_. +.. Test Case: `modify 1 t/` + + Expected: The first recipe's tags should be updated to contain *zero* tags. + +=== Finding a recipe + +. Finding recipes by name + +.. Prerequisite: *CookBuddy* contains a recipe with _Ham_ in its name. + + Test Case: `find n/Ham` + + Expected: Recipes with the word _Ham_ in their name are listed. +.. Prerequisite: *CookBuddy* contains _Ham Sandwich_ and _Idiot Sandwich_. + + Test Case: `find n/Ham Sandwich` + + Expected: Recipes whose name contains _Ham_ or _Sandwich_ are listed. + So both _Ham Sandwich_ and _Idiot Sandwich_ are listed. + +. Finding recipes by ingredient + +.. Prerequisite: *CookBuddy* contains a recipe with _bread_ in its ingredients. + + Test Case: `find ing/bread` + + Expected: Recipes whose ingredient names contain _bread_ are listed. + +. Finding recipes by instruction + +.. Prerequisite: *CookBuddy* contains a recipe with _ham_ in its instructions. + + Test Case: `find ins/bread` + + Expected: Recipes whose instructions contain _ham_ are listed. + + +=== Marking a recipe as done / not done + +[NOTE] +*CookBuddy* should contain at least one recipe. +If no recipe exists, delete the `data` folder and re-launch *CookBuddy*. +The recipe book should contain two recipes, _Ham Sandwich_ & _Idiot Sandwich_. + +. Marking a recipe as done + +.. Test Case: `done 1` + + Expected: The first recipe should be marked as done. +.. Test Case: `done n` (where n is larger than the list size) + + Expected: An error message is shown in the result pane prompting the user to enter a valid list index number. +.. Test Case: `done abc` + + Expected: An error message is shown in the result pane prompting the user to enter a valid integer. + +. Marking a recipe as not done + +.. Test Case: `undo 1` + + Expected: The first recipe should be marked as not done. +.. Test Case: `undo n` (where n is larger than the list size) + + Expected: An error message is shown in the result pane prompting the user to enter a valid list index number. +.. Test Case: `undo abc` + + Expected: An error message is shown in the result pane prompting the user to enter a valid integer. + + +=== Marking a recipe as a favourite / not a favourite + +[NOTE] +*CookBuddy* should contain at least one recipe. +If no recipe exists, delete the `data` folder and re-launch *CookBuddy*. +The recipe book should contain two recipes, _Ham Sandwich_ & _Idiot Sandwich_. + +. Marking a recipe as a favourite + +.. Test Case: `fav 1` + + Expected: The first recipe should be marked as favourite, indicated by the filled heart symbol. +.. Test Case: `fav n` (where n is larger than the list size) + + Expected: An error message is shown in the result pane prompting the user to enter a valid list index number. +.. Test Case: `fav abc` + + Expected: An error message is shown in the result pane prompting the user to enter a valid integer. + +. Un-marking a recipe as a favourite + +.. Test Case: `unfav 1` + + Expected: The first recipe should be un-marked as favourite, indicated by the un-filled heart symbol. +.. Test Case: `unfav n` (where n is larger than the list size) + + Expected: An error message is shown in the result pane prompting the user to enter a valid list index number. +.. Test Case: `unfav abc` + + Expected: An error message is shown in the result pane prompting the user to enter a valid integer. + +=== Adding a prep time to a recipe + +[NOTE] +*CookBuddy* should contain at least one recipe. +If no recipe exists, delete the `data` folder and re-launch *CookBuddy*. +The recipe book should contain two recipes, _Ham Sandwich_ & _Idiot Sandwich_. + +. Adding a prep time + +.. Test Case: `time 1 00:15` + + Expected: The first recipe's prep time should be updated to _15 minutes_. +.. Test Case: `time 1 00:15:30` + + Expected: The first recipe's prep time should be updated to _15 minutes and 30 seconds_. +.. Test Case: `time 1 00:63` + + Expected: An error message is shown in the result pane prompting the user to enter a _minutes_ value which is < 60. +.. Test Case: `time 1 00:15:65` + + Expected: Expected: An error message is shown in the result pane prompting the user to enter a _seconds_ value which is < 60. + +=== Deleting a recipe + +. Deleting a recipe while all recipes are listed + +.. Prerequisites: List all recipes using the `list` command. Have at least one recipe in the list. .. Test case: `delete 1` + - Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. + Expected: The first recipe is deleted from the list. Details of the deleted recipe is shown in the result pane. .. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. -.. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + + Expected: No recipe is deleted. Error message _"The recipe index provided is invalid"_ is shown in the result pane. +.. Test case: `delete n` (where n is larger than the list size) + Expected: Similar to previous. +.. Test case: `delete` + + Expected: No recipe is deleted. An error message prompting the user to provide an index is shown in the result pane. +.. Test case: `delete abc` + + Expected: No recipe is deleted. An error message prompting the user to provide a valid integer is shown in the result pane. -_{ more test cases ... }_ === Saving data -. Dealing with missing/corrupted data files +. Saving *CookBuddy's* recipe book to the save file. + +.. Enter any valid command that modifies data in the recipe book. +.. A file named _recipebook.json_ should be created in the _data/_ folder. + +. Dealing with missing/corrupted data in *CookBuddy's* save file -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ +.. Edit _recipebook.json_ and delete any recipe's _difficulty_ parameter and re-launch *CookBuddy*. +.. The recipe whose _difficulty_ was deleted will have defaulted back to 0 difficulty. -_{ more test cases ... }_ +// end::ManualTesting[] diff --git a/docs/Documentation.adoc b/docs/Documentation.adoc index ad90ac87bda..74ce19a1e9b 100644 --- a/docs/Documentation.adoc +++ b/docs/Documentation.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Documentation += CookBuddy - Documentation :site-section: DeveloperGuide :toc: :toc-title: @@ -12,7 +12,7 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S2-CS2103T-W12-4/main/tree/master == Introduction diff --git a/docs/LearningOutcomes.adoc b/docs/LearningOutcomes.adoc index 436c1777617..c1bc5023769 100644 --- a/docs/LearningOutcomes.adoc +++ b/docs/LearningOutcomes.adoc @@ -33,7 +33,7 @@ What other user stories do you think AddressBook should support? Add those user === Exercise: Add a 'Rename tag' use case * Add a use case to the `DeveloperGuide.adoc` to cover the case of _renaming of an existing tag_. -e.g. rename the tag `friends` to `buddies` (i.e. all persons who had the `friends` tag will now have +e.g. rename the tag `friends` to `buddies` (i.e. all recipes who had the `friends` tag will now have a `buddies` tag instead) Assume that AddressBook confirms the change with the user before carrying out the operation. @@ -108,7 +108,7 @@ image::PrintableInterface.png[width=400] String getPrintableString(Printable... printables) { ---- + -The above method can be used to get a printable string representing a bunch of person details. +The above method can be used to get a printable string representing a bunch of recipe details. For example, you should be able to call that method like this: + [source,java] diff --git a/docs/SettingUp.adoc b/docs/SettingUp.adoc index c0659782fab..b2d57356e10 100644 --- a/docs/SettingUp.adoc +++ b/docs/SettingUp.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Setting Up += CookBuddy - Setting Up :site-section: DeveloperGuide :toc: :toc-title: @@ -12,11 +12,11 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S2-CS2103T-W12-4/main/tree/master == Prerequisites -. *JDK `11`* or above +. *JDK `11`* . *IntelliJ* IDE + [NOTE] @@ -37,7 +37,7 @@ Do not disable them. If you have disabled them, go to `File` > `Settings` > `Plu == Verifying the setup -. Run the `seedu.address.Main` and try a few commands +. Run `cookbuddy.Main` and try a few commands . <> to ensure they all pass. == Configurations to do before writing code @@ -57,9 +57,7 @@ Optionally, you can follow the <> docume === Updating documentation to match your fork -After forking the repo, the documentation will still have the SE-EDU branding and refer to the `se-edu/addressbook-level3` repo. - -If you plan to develop this fork as a separate product (i.e. instead of contributing to `se-edu/addressbook-level3`), you should do the following: +If you plan to develop this fork as a separate product (i.e. instead of contributing to `AY1920S2-CS2103T-W12/main`), you should do the following: . Configure the <> in link:{repoURL}/build.gradle[`build.gradle`], such as the `site-name`, to suit your own project. @@ -81,4 +79,4 @@ Having both Travis and AppVeyor ensures your App works on both Unix-based platfo === Getting started with coding -When you are ready to start coding, we recommend that you get some sense of the overall design by reading about <>. +When you are ready to start coding, we recommend that you get some sense of the overall design by reading about <>. diff --git a/docs/Testing.adoc b/docs/Testing.adoc index 5767b92912c..603318e81b4 100644 --- a/docs/Testing.adoc +++ b/docs/Testing.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Testing += CookBuddy - Testing :site-section: DeveloperGuide :toc: :toc-title: @@ -12,7 +12,7 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S2-CS2103T-W12-4/main/tree/master == Running Tests @@ -35,11 +35,11 @@ See <> for more info on how to run tests using G We have three types of tests: . _Unit tests_ targeting the lowest level methods/classes. + -e.g. `seedu.address.commons.StringUtilTest` +e.g. `cookbuddy.commons.util.StringUtilTest` . _Integration tests_ that are checking the integration of multiple code units (those code units are assumed to be working). + -e.g. `seedu.address.storage.StorageManagerTest` +e.g. `cookbuddy.storage.StorageManagerTest` . Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together. + -e.g. `seedu.address.logic.LogicManagerTest` +e.g. `cookbuddy.logic.parser.RecipeBookParserTest` == Troubleshooting Testing diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 4e5d297a19f..130f885d191 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,7 +1,7 @@ -= AddressBook Level 3 - User Guide +# CookBuddy Recipe Manager :site-section: UserGuide :toc: -:toc-title: +:toc-title: Table Of Contents :toc-placement: preamble :sectnums: :imagesDir: images @@ -11,167 +11,836 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: +:warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3 +:repoURL: https://github.com/AY1920S2-CS2103T-W12-4/main/ -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `AY1920S2-CS2103T-W12-4` Since: `Feb 2020` Licence: `MIT` == Introduction -AddressBook Level 3 (AB3) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB3 is *optimized for those who prefer to work with 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. Interested? Jump to the <> to get started. Enjoy! +=== Product Information [Done by Zain Alam] +Greetings, and welcome to CookBuddy! + +Looking for an all-in-one solution to manage your recipe? Look no further! + +*CookBuddy* is an integrated platform fully customized for users with the aim of helping you manage your recipes effectively. The application allows you to keep track of your recipes. +It looks like you will never have to worry about finding your recipe anymore! + +CookBuddy uses a graphical user interface (GUI) to display your recipes, while allowing interaction using text, which is optimised for users familiar with the command-line, and are fast typists. + +If you are looking for a way to easily manage your recipes and have quick fingers, then CookBuddy is definitely for you! + +Interested? +Proceed to <> to get started! + +=== How to navigate this User Guide + +This user guide provides documentation on CookBuddy such as a quick start guide and an overview of its features for you to fully utilise CookBuddy. + +If you want to find out more about CookBuddy's features and commands, you can go to the <> section. + +If you need an overview regarding the usage of CookBuddy's commands, head on to the <> section. + +Throughout this guide, you may come across a few symbols. +They are used to draw your attention to important or note-worthy information. + +Here are the symbols used in this User Guide: + +[NOTE] +This block displays additional *notes* that contains extra information that you should take note of. + +[TIP] +This block displays *tips* that you may find useful. + +[CAUTION] +This block displays *cautions* that you should exercise. + +// tag::QuickStart[] == Quick Start -. Ensure you have Java `11` or above installed in your Computer. -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. -. Double-click the file to start the app. The GUI should appear in a few seconds. +The following steps will walk you through installing *CookBuddy* on your computer. + +. Ensure you have Java 11 or later installed on your computer. + - If not, download and install the latest Java Runtime Environment from https://www.java.com/en/download/[here]. + +. Download the latest CookBuddy.jar from https://github.com/AY1920S2-CS2103T-W12-4/main/releases[here]. + +. Copy the executable to the folder you want to use as the home folder for *CookBuddy*. + +. Double-click the executable to start *CookBuddy*. CookBuddy's GUI should appear in a few seconds as shown in the diagram below: + +[caption=] +.Home page of *CookBuddy* image::Ui.png[width="790"] + -. Type the command in the command box and press kbd:[Enter] to execute it. + -e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. -. Some example commands you can try: +. Type your command in the command box and press kbd:[Enter] to execute it. + e.g. typing `help` and pressing kbd:[Enter] will open the help window. -* *`list`* : lists all contacts -* **`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. -* **`delete`**`3` : deletes the 3rd contact shown in the current list -* *`exit`* : exits the app +. Some example commands you can try: -. Refer to <> for details of each command. + * `list` : lists all the recipes stored in CookBuddy. -[[Features]] -== Features + * `new n/Ham Sandwich ing/bread, 2 slices; ham, 1 slice ins/put ham between bread; serve on plate`: + adds a recipe named "Ham Sandwich" to *CookBuddy*. -==== -*Command Format* + * `delete 3` : deletes the 3rd recipe shown in the current list from CookBuddy. -* 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`. -* 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`. -* Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. -* 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. -==== + * `exit` : quits *CookBuddy*. -=== Viewing help : `help` +. Refer to <> for details of each command. -Format: `help` +// end::QuickStart[] -=== Adding a person: `add` +== Understanding CookBuddy’s GUI [Done by Zain Alam] -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +This section teaches you on how to utilise *CookBuddy’s* GUI. -[TIP] -A person can have any number of tags (including 0) +[caption=] +.The different components of CookBuddy's GUI +image::user-guide/ui-components.png[width="600"] + +There are four major components that you will be using in *CookBuddy* as seen in the diagram above and will be referenced in the upcoming sections. -Examples: +. *Menu bar* + +The menu bar contains clickable buttons that you can use to execute certain commands, such as help, `help` to open the help window. -* `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` +. *Command Line* + +_The command line is where you enter all your commands in *CookBuddy*._ + ++ +After entering your command, you can execute it by simply using the kbd:[Enter] key on your keyboard! -=== Listing all persons : `list` +. *Result Display* + +The result display displays feedback from *CookBuddy* to you after you have executed a command in *CookBuddy*. -Shows a list of all persons in the address book. + -Format: `list` +. *Status bar* + +The status bar shows you the path where your data is saved when you are using the features of *CookBuddy*. -=== Editing a person : `edit` +== Features -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +The following sub-sections describes the features you can use in *CookBuddy*. +.COMMAND FORMAT **** -* 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. -* 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. +* Words in `UPPER_CASE` are values of the parameters to be supplied by the user. + ** In `find n/RECIPE_NAME`, `RECIPE_NAME` refers to the value of the `n/` parameter supplied to the `find` command. + +* Words in square brackets indicate that they are optional. `[t/TAG]` means the `t/` parameter is optional. **** -Examples: +[CAUTION] +Please follow the format specified *strictly* and carefully. -* `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. +=== Command History [Done by Kevin] -=== Locating persons by name: `find` +You can view previously entered commands with kbd:[Page Up] and kbd:[Page Down]. -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +Ensure the _Command Box_ is in focus. -**** -* 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` -**** +To view the previous command in the command history, press kbd:[Page Up]. + +To view the next command in the command history, press kbd:[Page Down]. + +=== Help — `help` +You can list all the commands recognised by *CookBuddy* by typing `help` into the command box and pressing kbd:[Enter]. + +If you also specify a command after typing help, *CookBuddy* will show how to use that specific command. -Examples: +Format: `help [COMMAND]` -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +TIP: You can also execute this command by using the kbd:[F1] key on your keyboard. -// tag::delete[] -=== Deleting a person : `delete` +Example 1: You can type `help` and *CookBuddy* will display the commands that it recognises. + +*Expected Outcome* + +A separate help window will appear. + +image::user-guide/helpwindow.png[width="600"] + +Example 2: You can type `help delete` and *CookBuddy* will show you how to use the `delete` command. + +*Expected Outcome* + +A separate help window will appear with details on the delete command's usage. + +image::user-guide/helpwindowdelete.png[width="600"] + +// tag::AddRecipe[] +=== Adding a recipe — `new` [Done by Kevin] +You can add a new recipe to *CookBuddy* using the `new` command. + +[NOTE] +Parameters in *bold* indicate they are mandatory. + +Format: `new *n/NAME* *ing/INGREDIENT, QUANTITY* [; ...] *ins/INSTRUCTION* [; ...] [p/PATH] [cal/CALORIES] [s/SERVING_SIZE] +[r/RATING] [t/TAG [, ...]]` + +[CAUTION] +Remember to follow the format specified *strictly* and carefully. + +*CookBuddy* accepts the following parameters: + + * `n/` - *name of the recipe* + * `ing/` - *ingredients in the recipe* + * `ins/` - *instructions to follow* + * `p/` - path of the recipe's photo + * `cal/` - calories in the recipe (in kcal) + * `s/` - serving size of the recipe (any integer greater than 0) + * `r/` - personal rating of the recipe (any integer between 0-5 inclusive) + * `d/` - difficulty in following the recipe (any integer between 0-5 inclusive) + * `t/` - tags (separated by commas) + +Example 1: You can enter `new n/Fried Rice ing/White rice, 1 cup; salt, 1 gram ins/Fry the rice; add salt` into *CookBuddy*, and a new recipe with the following attributes will be added: + + * Name: Fried Rice + * Ingredients: + ** 1 cup of white rice + ** 1 gram of salt + * Instructions: + . Fry the rice + . Add salt + +*Expected Outcome* + +[caption=] +.1) If you would like to add a recipe to *CookBuddy*, enter the `new` command including the attributes of the recipe +image::user-guide/new-before.png[width="600"] + +[caption=] +.2) After using the `new` command, the new recipe will be added to *CookBuddy* and will be displayed. +image::user-guide/new-after.png[width="600"] + +// end::AddRecipe[] + +=== Viewing a Recipe — `view` +You can view a recipe at the given index on *CookBuddy* using the `view` command. + +Format: `view INDEX` + +[NOTE] +==== +`INDEX` must be a positive integer value, within the range of the number of recipes in your CookBuddy. +==== + +Example: You can type `view 2` and *CookBuddy* will display you the recipe at index 2 on the main page. + +*Expected Outcome* + +[caption=] +.1) You want to view the second recipe in *CookBuddy* +image::user-guide/view-before.png[width="600"] + +[caption=] +.2) After using the `view` command, the recipe will be displayed to you +image::user-guide/view-after.png[width="600"] + +// tag::TimeCommand[] +=== Assigning a time to a recipe — `time` [Done by: Adarsh Chugani] +You can assign a time to an existing recipe from *CookBuddy* using the `time` command. + +Format: `time INDEX TIMING` + +[NOTE] +==== +* `INDEX` must be a positive integer value, within range of the number of recipes in *CookBuddy*. +* The timing specified must be in the format hh:MM:ss +* for the timing, it is optional to include minutes and seconds. If they are omitted, they will be set to 0. +==== + +Example: You can type `time 1 00:10:00` and *CookBuddy* will assign the recipe at index 1 a timing of 10 minutes. + +*Expected Outcome* + +[caption=] +.1) You want to assign the first `recipe` in CookBuddy a timing of 10 minutes. +image::user-guide/timecommand-before.png[width="600"] + +[caption=] +.2) After typing in `time 1 00:10:00` command, the recipe time will be updated accordingly +image::user-guide/timecommand-after.png[width="600"] +// end::TimeCommand[] + +=== Deleting a recipe — `delete` +You can delete an existing recipe from *CookBuddy* using the `delete` command. -Deletes the specified person from the address book. + Format: `delete INDEX` -**** -* 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, ... -**** +Example: You can type `delete 1` and *CookBuddy* will delete the recipe at index 1. + +[NOTE] +==== +* `INDEX` must be a positive integer value, within the range of the number of recipes in *CookBuddy*. +==== + +*Expected Outcome* + +[caption=] +.1) You want to delete the first recipe in *CookBuddy* +image::user-guide/delete-before.png[width="600"] + +[caption=] +.2) After using the `delete` command, the recipe will be removed from *CookBuddy* +image::user-guide/delete-after.png[width="600"] + +[IMPORTANT] +==== +This command cannot be undone. Once a `recipe` has been deleted, its respective data entry in the save file will be permanently deleted. ++ + +//See <> for more details. +// +==== + +// tag::ModifyRecipe[] +=== Modifying a recipe — `modify` +You can modify the attributes of an existing recipe in *CookBuddy* using the `modify` command. + +Format: `modify INDEX [ing/INGREDIENT, QUANTITY [; ...]] [ins/INSTRUCTION [; ...]] [cal/CALORIES] [s/SERVING_SIZE] +[r/RATING] [t/TAG [, ...]]` + +[NOTE] +==== +`INDEX` must be a positive integer value, within range of the number of recipes in *CookBuddy*. +==== + +[CAUTION] +Remember to follow the format specified *strictly* and carefully. + +==== Modifying a recipe's ingredients +You can modify a recipe's ingredients by appending `ing/INGREDIENT, QUANTITY [; ...]` to a `modify` command. + +Example: You can type `modify 1 ing/ham, 2 slices` and *CookBuddy* will modify the ingredients of the 1st recipe in +the list to contain 2 slices of ham. + +//*Expected Outcome* +// +//[caption=] +//.1) You want to modify the `recipe ingredients` in your CookBuddy +//image::user-guide/modifyingredient-before.png[width="600"] +// +//[caption=] +//.2) After using the `modify` command, the `recipe ingredients` will be changed accordingly +//image::user-guide/modifyingredient-after.png[width="600"] + + +==== Modifying a recipe's instructions +You can modify a recipe's instructions by appending `ins/INSTRUCTION [; ...]` to a `modify` command. + +Example: You can type `modify 2 ins/boil eggs; slice apples` and *CookBuddy* will modify the instructions in the 2nd +recipe to contain two instructions: + + * boil eggs + * slice apples + +//*Expected Outcome* +// +//[caption=] +//.1) You want to favourite the first `recipe` in your CookBuddy +//image::user-guide/fav-before.png[width="600"] +// +//[caption=] +//.2) After using the `modify` command, the `recipe tags` will be changed accordingly +//image::user-guide/modifytag-after.png[width="600"] + + +==== Modifying a recipe's tags +You can modify a recipe's tags by appending `[t/TAG [, ...]]` to a `modify` command. +If you want to remove all the tags from the recipe, append `t/` instead. + +Example 1: You can type `modify 2 t/lunch, dinner` and *CookBuddy* will update the tags in the 1st recipe to contain +two tags: + + * lunch + * dinner + +*Expected Outcome* + +[caption=] +.1) You want to modify a recipe's tags in *CookBuddy* +image::user-guide/modifytag-before.png[width="600"] + +[caption=] +.2) After using the `modify` command, the recipe's tags will be changed accordingly +image::user-guide/modifytag-after.png[width="600"] + +Example 2: You can type `modify 1 t/` and *CookBuddy* will remove all the existing tags from the 1st recipe. + +*Expected Outcome* + +[caption=] +.1) You want to remove all the tags from a recipe in *CookBuddy* +image::user-guide/modifyremovetag-before.png[width="600"] + +[caption=] +.2) After using the `modify` command, the recipe's tags will be removed accordingly +image::user-guide/modifyremovetag-after.png[width="600"] + +// end::ModifyRecipe[] + +// tag::FavCommand[] +=== Favouriting a recipe — `fav` [Done by: Adarsh Chugani] +You can favourite an existing recipe from *CookBuddy* using the `fav` command. + +Format: `fav INDEX` + +[NOTE] +==== +* `INDEX` must be a positive integer value, within range of the number of recipes in *CookBuddy*. +* A favourited recipe is indicated by a red filled heart. +==== + +Example: You can type `fav 1` and *CookBuddy* will favourite the recipe at index 1. + +//*Expected Outcome* +// +//[caption=] +//.1) You want to favourite the first `recipe` in your CookBuddy +//image::user-guide/fav-before.png[width="600"] +// +//[caption=] +//.2) After using the `modify` command, the `recipe tags` will be changed accordingly +//image::user-guide/modifytag-after.png[width="600"] +// end::FavCommand[] + +// tag::UnFavCommand[] +=== Un-Favouriting a recipe — `unfav` [Done by: Adarsh Chugani] +You can un-favourite an existing recipe from *CookBuddy* using the `unfav` command. + +Format: `unfav INDEX` + +[NOTE] +==== +* `INDEX` must be a positive integer value, within range of the number of recipes in your CookBuddy. +* Recipes are not favourited by default. +* This command is only useful if you wish to un-favourite a recipe that is already favourited. +* A non-favourited recipe is indicated by a heart with a read outline and no fill. +==== + +//*Expected Outcome* +// +//[caption=] +//.1) You want to favourite the first `recipe` in your CookBuddy +//image::user-guide/fav-before.png[width="600"] +// +//[caption=] +//.2) After using the `modify` command, the `recipe tags` will be changed accordingly +//image::user-guide/modifytag-after.png[width="600"] + +// end::UnFavCommand[] + + +// tag::DoneCommand[] +=== Marking a recipe as done — `done` [Done by: Adarsh Chugani] +You can mark an existing recipe from *CookBuddy* as being done using the `done` command. This indicates that the recipe +has been attempted. + +Format: `done INDEX` + +[NOTE] +==== +* `INDEX` must be a positive integer value, within the range of the number of recipes in your CookBuddy. + +==== + +Example: You can type `done 1` and *CookBuddy* will mark the recipe at index 1 as being done, indicating that it has +been attempted. + +//*Expected Outcome* +// +//[caption=] +//.1) You want to favourite the first `recipe` in your CookBuddy +//image::user-guide/fav-before.png[width="600"] +// +//[caption=] +//.2) After using the `modify` command, the `recipe tags` will be changed accordingly +//image::user-guide/modifytag-after.png[width="600"] +// end::DoneCommand[] + + +// tag::UndoCommand[] +=== Un-Marking a recipe as done — `undo` [Done by: Adarsh Chugani] +You can un-mark an existing recipe from *CookBuddy* as being done, using the`undo` command. This indicates that the +recipe has not been attempted. + +Format: `undo INDEX` + +[NOTE] +==== +* `INDEX` must be a positive integer value, within range of the number of recipes in your CookBuddy. +* Recipes are marked as not attempted by default. +* This command is only useful if you wish to un-mark recipe that is already marked as done. +==== + +Example: You can type `undo 1` and *CookBuddy* will un-mark the recipe at index 1 as done, indicating that it has not +been attempted. + +//*Expected Outcome* +// +//[caption=] +//.1) You want to favourite the first `recipe` in your CookBuddy +//image::user-guide/fav-before.png[width="600"] +// +//[caption=] +//.2) After using the `modify` command, the `recipe tags` will be changed accordingly +//image::user-guide/modifytag-after.png[width="600"] +// end::UndoCommand[] + +=== Listing recipes — `list` +You can list all the existing recipes from *CookBuddy* using the `list` command. This command also helps to refresh +the current recipe list. + +Format: `list` + +Example: You can type `list` and *CookBuddy* will display all the recipes that are currently stored in it. + +//*Expected Outcome* +// +//[caption=] +//.1) You want to favourite the first `recipe` in your CookBuddy +//image::user-guide/fav-before.png[width="600"] +// +//[caption=] +//.2) After using the `modify` command, the `recipe tags` will be changed accordingly +//image::user-guide/modifytag-after.png[width="600"] + +// tag::FindRecipe[] +=== Finding recipes — `find` [Done by Kevin] +You can find an existing recipe with a particular attribute from *CookBuddy* using the `find` command. + +Format: `find [n/NAME [...]]` + +Format: `find [ing/INGREDIENT 1, QUANTITY [; ...]]` + +Format: `find [ins/INSTRUCTION [; ...]]` + +[NOTE] +==== +*CookBuddy* can find recipes from one parameter at a time. +==== + +==== Finding a recipe by name +You can find a recipe by its name by running `find n/NAME [...]`. + +Example: You can type `find n/Ham` and *CookBuddy* will display recipes that contain the word *Ham* in their name. + +*Expected Outcome* + +[caption=] +.1) You want to find recipes that contain *Ham* in their name +image::user-guide/findName-before.png[width="600"] + +[caption=] +.2) After using the `find n/Ham` command, recipes that contain *Ham* in their name are displayed. +image::user-guide/findName-after.png[width="600"] + + +==== Finding a recipe by ingredient +You can find a recipe by its ingredients by running `find ing/INGREDIENT [...]`. + +Example: You can type `find ing/bread` and *CookBuddy* will display the recipes containing *bread* as an ingredient to you. + +[caption=] +.1) You want to find recipes that contain *bread* in their ingredients +image::user-guide/findIngredients-before.png[width="600"] -Examples: +[caption=] +.2) After using the `find ing/bread` command, recipes that contain *bread* in their ingredients are displayed. +image::user-guide/findIngredients-after.png[width="600"] -* `list` + -`delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + -`delete 1` + -Deletes the 1st person in the results of the `find` command. +==== Finding a recipe by instructions +You can find a recipe by its instructions by running `find ins/INSTRUCTION [...]`. -// end::delete[] -=== Clearing all entries : `clear` +Example: You can type `find ins/head` and *CookBuddy* will display the recipes containing *head* in its instructions to you. -Clears all entries from the address book. + -Format: `clear` +[caption=] +.1) You want to find recipes that contain *head* in their instructions +image::user-guide/findInstructions-before.png[width="600"] -=== Exiting the program : `exit` +[caption=] +.2) After using the `find ins/head` command, recipes that contain *head* in their ingredients are displayed. +image::user-guide/findInstructions-after.png[width="600"] + +// end::FindRecipe[] + +=== Counting recipes — `count` +You can count the total number of recipes stored in *CookBuddy* using the `count` command. + +Format: `count` + +Example: You can type `count` and *CookBuddy* will display the total number of recipes stored in it. + +//*Expected Outcome* +// +//[caption=] +//.1) You want to favourite the first `recipe` in your CookBuddy +//image::user-guide/fav-before.png[width="600"] +// +//[caption=] +//.2) After using the `modify` command, the `recipe tags` will be changed accordingly +//image::user-guide/modifytag-after.png[width="600"] +// +// +// === Duplicate Recipe — `dup ` +// Duplicates the recipe found at the specified index, and places the new recipe at `index + 1` +// Useful for users who wish to experiment with recipes while keeping a copy of the original. + + +=== Deleting all recipes — `reset` +You can remove all the recipes stored in *CookBuddy* using the `reset`command. + +Format: `reset` + +Example: You can type `reset` and *CookBuddy* will clear all the recipes stored in it. + +//*Expected Outcome* +// +//[caption=] +//.1) You want to favourite the first `recipe` in your CookBuddy +//image::user-guide/fav-before.png[width="600"] +// +//[caption=] +//.2) After using the `modify` command, the `recipe tags` will be changed accordingly +//image::user-guide/modifytag-after.png[width="600"] + + +=== Exiting CookBuddy — `exit` +You can exit from *CookBuddy* using the `exit` command. -Exits the program. + Format: `exit` -=== Saving the data +Example: You can type `exit` and *CookBuddy* will terminate. + +== Configuration and Recipe Data [Sharadh] +=== Configuration +CookBuddy may be easily configured, by editing the key-value pairs in `preferences.json`, which is automatically created at the same folder where the CookBuddy `jar` file is. The key-value pairs are detailed in <>. + +[[configoptions]] +[options="header",width=700] +.`preferences.json` key-value pairs +|=== +|Key|Default value|Valid values +|`windowWidth`|740.0|Any positive floating-point (decimal) number +|`windowHeight`|600.0|Any positive floating-point (decimal) number +|`windowCoordinates: x`|398|Any positive integer +|`windowCoordinates: y`|88|Any positive integer +| `dataFilePath`|`data/recipebook.json` on *nix, `\data\\recipebook.json` on Windows | Any valid file path appropriate to the OS +|"recipeImagePath" | `data/images` on *nix, `data\\images` on Windows | Any valid file path appropriate to the OS +|=== + +=== Recipe data +The recipe data is stored in the specified path above, as _another_ `.json` file, with key-value pairs. + +[WARNING] +==== +Do *_not_* edit the `recipebook.json` file by hand, nor delete any lines from the file. CookBuddy may fail to read the file and may crash, or worse, overwrite the file with placeholder data. + +The file may be copied out elsewhere for editing, but adhere strictly to the format as written out by CookBuddy. +==== + +=== Image data +Each recipe's image is stored as a `.png` file in the pecified folder, with the recipe name, and a unique identifier (UID). If a given recipe does not have an image, CookBuddy will automatically use a placeholder instead, and denote so in `recipebook.json`. + + + +// tag::FutureStuff[] +== Coming in v2.0 [Done by: Adarsh Chugani] + +Look forward to these features coming up in version 2.0 of CookBuddy! + + +=== Adding price *(coming in v2.0)*: `price` + +Adds the price attribute to recipes. +With this, to can assign every recipe a specific price. + +Format: `price INDEX PRICE` + +Inputs from user: + +* INDEX: the index of the recipe you wish to assign a price to + +* PRICE: the price that you wish to assign to the recipe (accepts both integer and decimal values) + +Example: `price 1 10.50` + +This would assign the recipe at index a price of $10.50 + + +=== Viewing statistics *(coming in v2.0)*: `stats` + +Format: `stats` + +Displays your statistics: such as percentage of recipes attempted, difficulty breakdown of attempted recipes, amongst many others. +With this feature, you will be able to analyse your cooking preferences! + + +=== Suggestions *(coming in v2.0)*: `suggest` + +Format: `suggest` + +This feature suggests a recipe from your CookBuddy application for you to attempt. +You will be suggested a recipe based on your statistics, namely the difficulty and ratings of the recipes you have attempted. +The suggested recipe is more likely to be a recipe that you have not attempted, as we do not want you to constantly cook the same recipes. +Additionally, the suggested recipe is more likely to have a difficulty similar to, or slightly higher than the recipes you have attempted, as we want you to grow as a budding cook! + +==== Scale the recipe *(coming in v2.0)*: `scale` +This feature scales a recipe to your intended size. +The quantities of the ingredients used, as well as the prep time required will be adjusted for your use. + +Format: `scale INDEX SIZE` + +Inputs from user: + +* INDEX: The index of the recipe you wish to scale + +* SIZE: The size that you wish to scale the recipe to + + +Example: scale 1 3 + +This would scale the recipe at index 1 to be able to serve 3 people. + + + +=== Import and export *(coming in v2.0)*: `import`, `export` + +==== Import a file: `import` + +Imports recipes from the file at the given path. + +Format: `import PATH` + +Inputs from user: + +* PATH: the file path at which the desired file is + +Example: import "/docs/recipebook.json" + +This would import the "recipebook" JSON file from the docs folder. + +==== Export transactions: `export` + +Exports all transactions to the given path. + +Format: `export PATH` + +Inputs from user: + +* PATH: the file path at which the desired file is + +Exports the recipes from cookbuddy into the given path + +Example: export "/docs/" + +This would export the recipes from CookBuddy into a file in the docs folder. + + + + +=== Printing *(coming in v2.0)*: `print` + +Prints out the recipe. + +Format: `print INDEX` + +Key-Words: +* INDEX: the index of the recipe you wish to print. + +Example: `print 1` + +This would print out the recipe at index 1. + + +=== Sharing Recipes *(coming in v2.0)*: `share` + +Prints out the recipe. + +Format: `share INDEX SITE` + +Key-Words: +* INDEX: the index of the recipe you wish to share. + +* SITE: the website you wish to share the recipe to + +Example: `share 1 facebook` + +This would share the recipe at index 1 on the user's facebook account. +// end::FutureStuff[] + + + + + +== FAQ [Done by Zain Alam] + +The following section answers some questions you might have regarding *CookBuddy*. + +*Q: Is *CookBuddy* safe to use?* + +*A*: Yes, *CookBuddy* is safe to use. We regularly review our code to ensure that there are no vulnerabilities for hackers to exploit. + +*Q: Is *CookBuddy* secure?* + +*A*: Yes, *CookBuddy* is secure. Your data is stored only on your computer. No data is sent to any online server. + +*Q: Do I need an Internet connection to use *CookBuddy*?* + +*A*: No. *CookBuddy* works 100% offline. + +*Q: Will *CookBuddy* receive updates?* + +*A*: Yes. We are a dedicated team of software developers who are constantly collating feedback and running tests on the app. We are also working towards delivering additional features for our users. + +*Q: Can I use CookBuddy on a mobile device?* + +*A*: *CookBuddy* is designed to work best on a desktop/ laptop computer. We are currently working on releasing *CookBuddy* on mobile platforms. + +*Q: How do I transfer my data to another Computer?* + +*A*: Download the jar in the other computer and copy the entire data folder over to the same directory. Run *CookBuddy* and update the preferences.json if necessary. + +*Q: Can I edit my data folder of *CookBuddy* which contains the information regarding my recipes?* + +*A*: No. Please do not modify the data folder of *CookBuddy*. All modifications of the recipes should be done through the `modify` +command only. + +*Q: How do I retrieve back all the recipes in *CookBuddy* if I accidentally reset *CookBuddy*?* + +*A*: Right now *CookBuddy* does not support a backup feature. Thus, it would be best if you do not accidentally use the `reset` +command. The backup feature will be released soon in the near future. + +*Q: *CookBuddy* is not working on my computer. How do I fix it?* + +*A*: Ensure that your computer is running on Java 11 and not other versions. *CookBuddy* does not support other versions of Java. -Address book data are saved in the hard disk automatically after any command that changes the data. + -There is no need to save manually. +== Command Summary [Done by Zain Alam] -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +The following section gives a quick summary of all the commands you can use in CookBuddy arranged in an alphabetical order in the table below. -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +[cols="1, 3", options="header"] +.Various commands of *CookBuddy*. +|============== +|Command | Usage +| `count` | Counts the total number of recipes stored in CookBuddy. +| `delete *INDEX*` | Deletes the recipe at the given `INDEX`. +| `done *INDEX*` | Marks the recipe at the given `INDEX` as `done`. +| `exit` | Exits CookBuddy. +| `fav *INDEX*` | Favourites the recipe at the given `INDEX`. +| `find [n/NAME [...]] [ing/INGREDIENT [...]]` | Finds an existing recipe with the given parameter(s) from CookBuddy. +| `help` | Lists all the commands recognised by CookBuddy. +| `help [COMMAND]` | Displays how to use the `COMMAND` command. +| `list` | Lists all the recipes. +| `modify INDEX [ing/INGREDIENT, QUANTITY [; ...]] [ins/INSTRUCTION [; ...]] [cal/CALORIES] [s/SERVING_SIZE] +[r/RATING] [t/TAG [, ...]]` | Modifies the given parameter(s) of the recipe. +| `new *n/NAME* *ing/INGREDIENT, QUANTITY* [; ...] *ins/INSTRUCTION* [; ...] [p/PATH] [cal/CALORIES] [s/SERVING_SIZE] +[r/RATING] [t/TAG [, ...]]` | Adds a new recipe. +| `reset` | Removes all the recipes from CookBuddy. +| `undo *INDEX*` | Undoes the recipe at the given `INDEX`. +| `unfav *INDEX*` | Un-favourites the recipe at the given `INDEX`. +| `view *INDEX*` | Displays the recipe at the given `INDEX` on CookBuddy. -== 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 Address Book folder. +== Glossary [Done by Zain Alam] +This section will cover and explain certain technical/CookBuddy specific terms used in this user guide in the table below. -== Command Summary +.Explanation of various terms used in this user guide. +[cols="1, 3", options="header"] +|============== +|Term | Explanation +|Command Line Interface (CLI) | A user interface where a user is required to use the program by entering commands into a text box. +|Graphical User Interface (GUI) | A user interface that includes visuals such as buttons, icons, images, menus etc. +|Metadata | Details associated with an entity. For example, metadata of a `recipe` include the `name`, `ingredients`, `instructions` etc. -* *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` +|============== diff --git a/docs/UsingNetlify.adoc b/docs/UsingNetlify.adoc index 2e108c936a3..a4274692e18 100644 --- a/docs/UsingNetlify.adoc +++ b/docs/UsingNetlify.adoc @@ -24,7 +24,7 @@ https://www.netlify.com/[Netlify] is an automated hosting platform for deploying . You will then be brought to the setup page. Click `GitHub` to link your repository to Netlify. * Depending on whether you are the owner of the repository, you can either grant or request access to Netlify so that it can access your repository and build your documentation. + -image:netlify/grant_or_request_access.png[Grant or request access, width = 630] +image:netlify/grant_or_request_access.svg[Grant or request access, width = 630] * After granting or requesting access to your repository, click `Authorize netlify`. + . Pick your repository from the list. diff --git a/docs/UsingPlantUml.adoc b/docs/UsingPlantUml.adoc index cfe2533ea84..efeef312abf 100644 --- a/docs/UsingPlantUml.adoc +++ b/docs/UsingPlantUml.adoc @@ -107,7 +107,7 @@ By default, a short link (`\->`) points to right and a long link (`-\->`) points downwards. you can extend any link to make it longer (```--\->```). .Length of arrows and its effects -image::ArrowLength.png[] +image::ArrowLength.svg[] ==== Link directions Clever usage of arrow directions will resolve most layout issues. @@ -130,7 +130,7 @@ B --> 2 C --> 3 D --> 4 ---- -|image::AllDown.png[] +|image::AllDown.svg[] |[source, puml] ---- @@ -149,7 +149,7 @@ C -up-> 3 D -up-> 4 ---- -|image::UpAndDown.png[] +|image::UpAndDown.svg[] |[source, puml] ---- @@ -168,7 +168,7 @@ A -right[hidden]- B B -right[hidden]- C C -right[hidden]- D ---- -|image::HiddenArrows.png[] +|image::HiddenArrows.svg[] |=== ==== Reordering definitions @@ -185,7 +185,7 @@ If there is no formal definition, the objects is taken to be declared upon its f A --> B C --> D ---- -|image::ABeforeC.png[] +|image::ABeforeC.svg[] |[source, puml] ---- @@ -195,7 +195,7 @@ Class C A --> B C --> D ---- -|image::CBeforeA.png[] +|image::CBeforeA.svg[] |[source, puml] ---- @@ -205,7 +205,7 @@ package "Rule Of Thumb"{ C --> D } ---- -|image::PackagesAndConsistency.png[] +|image::PackagesAndConsistency.svg[] |=== TIP: Explicitly define all symbols to avoid any potential layout mishaps. diff --git a/docs/diagrams/ArchitectureDiagram.puml b/docs/diagrams/ArchitectureDiagram.puml index d021b3992ed..11459807395 100644 --- a/docs/diagrams/ArchitectureDiagram.puml +++ b/docs/diagrams/ArchitectureDiagram.puml @@ -1,10 +1,10 @@ -@startuml +@startuml ArchitectureDiagram !include !include !include !include style.puml -Package " "<>{ +Package "CookBuddy" <>{ Class UI UI_COLOR Class Logic LOGIC_COLOR Class Storage STORAGE_COLOR @@ -15,9 +15,8 @@ Package " "<>{ Class Hidden #FFFFFF Class HiddenUI #FFFFFF Class HiddenModel #FFFFFF - - } + Class "<$user>" as User MODEL_COLOR_T2 Class "<$documents>" as File UI_COLOR_T1 diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index d1e2ae93675..61348e2bdb2 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -1,4 +1,4 @@ -@startuml +@startuml ArchitectureSequenceDiagram !include style.puml Actor User as user USER_COLOR @@ -13,13 +13,13 @@ activate ui UI_COLOR ui -[UI_COLOR]> logic : execute("delete 1") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : deleteRecipe(r) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic deactivate model -logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) +logic -[LOGIC_COLOR]> storage : saveRecipeBook(recipeBook) activate storage STORAGE_COLOR storage -[STORAGE_COLOR]> storage : Save to file diff --git a/docs/diagrams/AttributeClassDiagram.puml b/docs/diagrams/AttributeClassDiagram.puml new file mode 100644 index 00000000000..00f526657e6 --- /dev/null +++ b/docs/diagrams/AttributeClassDiagram.puml @@ -0,0 +1,63 @@ +@startuml AttributeClassDiagram +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +package attribute { + Class Name + Class IngredientList + Class InstructionList + + Class Ingredient + Class Instruction + + Class Calorie + Class Difficulty + Class Done + Class Fav + Class Photograph + Class Rating + Class Serving + Class Tag + Class Time +} + +package recipe { + Class Recipe +} + +Recipe *-d-> "1" Name +Recipe *-d-> "1" IngredientList +Recipe *-d-> "1" InstructionList + +IngredientList *-d-> "1..*" Ingredient +InstructionList *-d-> "1..*" Instruction + +Recipe *--> "0..1" Calorie +Recipe *--> "0..1" Difficulty +Recipe *--> "0..1" Done +Recipe *--> "0..1" Fav +Recipe *-l-> "0..1" Photograph +Recipe *-l-> "0..1" Rating +Recipe *-l-> "0..1" Serving +Recipe *-l-> "0..*" Tag +Recipe *-l-> "0..1" Time + +' recipe-[hidden]-attribute + +' Name-[hidden]-IngredientList +' IngredientList-[hidden]-InstructionList +' InstructionList-[hidden]-Calorie + +' Calorie-[hidden]-Difficulty +' Difficulty-[hidden]-Done +' Done-[hidden]-Fav +' Fav-[hidden]-Photograph +' Photograph-[hidden]-Rating +' Rating-[hidden]-Serving +' Serving-[hidden]-Tag +' Tag-[hidden]-Time + + +@enduml diff --git a/docs/diagrams/BasicRecipeDiagram.puml b/docs/diagrams/BasicRecipeDiagram.puml new file mode 100644 index 00000000000..173aea8dc5f --- /dev/null +++ b/docs/diagrams/BasicRecipeDiagram.puml @@ -0,0 +1,127 @@ +@startuml BasicRecipeDiagram +show members + +hide circle +skinparam classAttributeIconSize 0 + +class RecipeBook { + -recipes : UniqueRecipeList + +addRecipe() + +deleteRecipe() + +list() + +duplicate() +} + +RecipeBook "1..*" *--> "many" Recipe : contains > + +class Recipe { + -name : String + -ingredients : IngredientList + -instructions : InstructionList + -tags : TagList + +modify() +} + +class IngredientList { + -data : ArrayList + +add() + +set() + +remove() + +list() +} + +class Ingredient { + -name : String + -quantity : Quantity +} + +IngredientStore <|--> Freezer : is a very cold & dry < +IngredientStore <|--> Refrigerator : is a cold < +IngredientStore <|--> Larder : is a cool, dark & dry < +IngredientStore "0..*" o-r-> "many" Ingredient : stores < + +class InstructionList { + -data : HashMap + +add() + +edit() + +list() + +remove() +} + +class Instruction { + -detail : String +} + +class TagList { + -data : ArrayList + +add() + +remove() +} + +class Tag { + -name : String +} + +TagList "0..*" o--> "many" Tag : contains > + + +class Quantity { + -amount : Float + -unit : Unit +} + +class Unit { + -name : String + -symbol : UnitSymbol + {static} +convertTo() +} + +enum UnitSymbol <> { + tsp + tbp + cup + ml + g + kg + lb + fl oz +} + +Recipe "1" *-l-> IngredientList : contains > +Recipe "1" *--> InstructionList : contains > +Recipe "1" *--> TagList : contains > + + +IngredientList "1..*" o-l-> "many" Ingredient : contains > +InstructionList "1..*" o--> "many" Instruction : contains > + +Ingredient *--> Quantity : has amount > +Quantity --> Unit : is measured in > +Unit "*" -- "1" UnitSymbol : has symbol > + +class Larder { + +} + +class Refrigerator { + -temperature : Double +} + +class Freezer { + -temperature : Double +} + +class IngredientStore { + -capacity : Integer + -store : IngredientAvailabilityList + +addIngredient() +} + +class IngredientAvailabilityList { + -availableIngredients : IngredientList + boolean : isAvailable() +} + +IngredientList <|-- IngredientAvailabilityList + +@enduml diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml index 7790472da52..03831949aae 100644 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ b/docs/diagrams/BetterModelClassDiagram.puml @@ -1,21 +1,23 @@ -@startuml +@startuml BetterModelClassDiagram !include style.puml skinparam arrowThickness 1.1 skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR -AddressBook *-right-> "1" UniquePersonList -AddressBook *-right-> "1" UniqueTagList -UniqueTagList -[hidden]down- UniquePersonList -UniqueTagList -[hidden]down- UniquePersonList +RecipeBook *-right-> "1" UniqueRecipeList +RecipeBook *-right-> "1" UniqueTagList +UniqueTagList -[hidden]down- UniqueRecipeList +UniqueTagList -[hidden]down- UniqueRecipeList UniqueTagList *-right-> "*" Tag -UniquePersonList o-right-> Person +UniqueRecipeList o-right-> Recipe -Person o-up-> "*" Tag +Recipe o-up-> "*" Tag -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address +Recipe *--> Name +Recipe *--> IngredientList +Recipe *--> InstructionList +Recipe *--> Calorie +Recipe *--> Serving +Recipe *--> Rating @enduml diff --git a/docs/diagrams/CommitActivityDiagram.puml b/docs/diagrams/CommitActivityDiagram.puml index 7f8fe407f89..d533b36a76c 100644 --- a/docs/diagrams/CommitActivityDiagram.puml +++ b/docs/diagrams/CommitActivityDiagram.puml @@ -1,14 +1,14 @@ -@startuml +@startuml CommitActivityDiagram start :User executes command; 'Since the beta syntax does not support placing the condition outside the 'diamond we place it as the true branch instead. -if () then ([command commits AddressBook]) +if () then ([command commits RecipeBook]) :Purge redunant states; - :Save AddressBook to - addressBookStateList; + :Save RecipeBook to + recipeBookStateList; else ([else]) endif stop diff --git a/docs/diagrams/CountActivityDiagram.puml b/docs/diagrams/CountActivityDiagram.puml new file mode 100644 index 00000000000..d21bbf8c2df --- /dev/null +++ b/docs/diagrams/CountActivityDiagram.puml @@ -0,0 +1,17 @@ +@startuml +!include style.puml + +start +:User inputs Count command; + +If () then ([command is valid format]) + :Count all the recipes; + :Returns success message; +else ([else]) + :Returns failure message + (Invalid Command); +endif +:LogicManager prints message to user; + +stop +@enduml diff --git a/docs/diagrams/CountSequenceDiagram.puml b/docs/diagrams/CountSequenceDiagram.puml new file mode 100644 index 00000000000..aa0cf6a0cb2 --- /dev/null +++ b/docs/diagrams/CountSequenceDiagram.puml @@ -0,0 +1,52 @@ +@startuml CountSequenceDiagram +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":RecipeBookParser" as RecipeBookParser LOGIC_COLOR +participant "d:CountCommand" as CountCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("count") +activate LogicManager + +LogicManager -> RecipeBookParser : parseCommand("count") +activate RecipeBookParser + +create CountCommand +RecipeBookParser -> CountCommand +activate CountCommand + +CountCommand --> RecipeBookParser : d +deactivate CountCommand + +RecipeBookParser --> LogicManager : d +deactivate RecipeBookParser + +LogicManager -> CountCommand : execute() +activate CountCommand + +CountCommand -> Model : count() +activate Model + +Model --> CountCommand +deactivate Model + +create CommandResult +CountCommand -> CommandResult +activate CommandResult + +CommandResult --> CountCommand +deactivate CommandResult + +CountCommand --> LogicManager : result +deactivate CountCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..3a19681a09e 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -1,9 +1,9 @@ -@startuml +@startuml DeleteSequenceDiagram !include style.puml box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":RecipeBookParser" as RecipeBookParser LOGIC_COLOR participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR participant ":CommandResult" as CommandResult LOGIC_COLOR @@ -16,17 +16,17 @@ end box [-> LogicManager : execute("delete 1") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") -activate AddressBookParser +LogicManager -> RecipeBookParser : parseCommand("delete 1") +activate RecipeBookParser create DeleteCommandParser -AddressBookParser -> DeleteCommandParser +RecipeBookParser -> DeleteCommandParser activate DeleteCommandParser -DeleteCommandParser --> AddressBookParser +DeleteCommandParser --> RecipeBookParser deactivate DeleteCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") +RecipeBookParser -> DeleteCommandParser : parse("1") activate DeleteCommandParser create DeleteCommand @@ -36,19 +36,19 @@ activate DeleteCommand DeleteCommand --> DeleteCommandParser : d deactivate DeleteCommand -DeleteCommandParser --> AddressBookParser : d +DeleteCommandParser --> RecipeBookParser : d deactivate DeleteCommandParser 'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser +DeleteCommandParser -[hidden]-> RecipeBookParser destroy DeleteCommandParser -AddressBookParser --> LogicManager : d -deactivate AddressBookParser +RecipeBookParser --> LogicManager : d +deactivate RecipeBookParser LogicManager -> DeleteCommand : execute() activate DeleteCommand -DeleteCommand -> Model : deletePerson(1) +DeleteCommand -> Model : deleteRecipe(1) activate Model Model --> DeleteCommand diff --git a/docs/diagrams/DoneActivityDiagram.puml b/docs/diagrams/DoneActivityDiagram.puml new file mode 100644 index 00000000000..abf66583ce1 --- /dev/null +++ b/docs/diagrams/DoneActivityDiagram.puml @@ -0,0 +1,30 @@ +@startuml +!include style.puml + +start +:User inputs done command; + +:Parser parses input; + +If () then ([Command is valid format]) + +If () then ([Recipe index exists]) + :Marks the recipe at specified index as attempted; + :Returns success message; +else ([else]) + :Returns failure message + (Invalid index); +endif +:LogicManager prints message to user; + +else ([else]) + :Returns failure message + (invalid command format); + +endif + +:Results are displayed to user; + + +stop +@enduml diff --git a/docs/diagrams/DoneSequenceDiagram.puml b/docs/diagrams/DoneSequenceDiagram.puml new file mode 100644 index 00000000000..3876af0dd88 --- /dev/null +++ b/docs/diagrams/DoneSequenceDiagram.puml @@ -0,0 +1,71 @@ +@startuml DoneSequenceDiagram +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":RecipeBookParser" as RecipeBookParser LOGIC_COLOR +participant ":DoneCommandParser" as DoneCommandParser LOGIC_COLOR +participant "d:DoneCommand" as DoneCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + + +[-> LogicManager : execute("done 1") +activate LogicManager + +LogicManager -> RecipeBookParser : parseCommand("done 1") +activate RecipeBookParser + +create DoneCommandParser +RecipeBookParser -> DoneCommandParser +activate DoneCommandParser + +DoneCommandParser --> RecipeBookParser +deactivate DoneCommandParser + +RecipeBookParser -> DoneCommandParser : parse("1") +activate DoneCommandParser + +create DoneCommand +DoneCommandParser -> DoneCommand +activate DoneCommand + +DoneCommand --> DoneCommandParser : d + +deactivate DoneCommand + +DoneCommandParser --> RecipeBookParser : d +deactivate DoneCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +DoneCommandParser -[hidden]-> RecipeBookParser +destroy DoneCommandParser + +RecipeBookParser --> LogicManager : d +deactivate RecipeBookParser + +LogicManager -> DoneCommand : execute() +activate DoneCommand + +DoneCommand -> Model : attemptRecipe(r) +activate Model + +Model --> DoneCommand +deactivate Model + +create CommandResult +DoneCommand -> CommandResult +activate CommandResult + +CommandResult --> DoneCommand +deactivate CommandResult + +DoneCommand --> LogicManager : result +deactivate DoneCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DuplicateCommandActivity.puml b/docs/diagrams/DuplicateCommandActivity.puml new file mode 100644 index 00000000000..d25c91266b5 --- /dev/null +++ b/docs/diagrams/DuplicateCommandActivity.puml @@ -0,0 +1,16 @@ +@startuml +!include style.puml + +start +:User inputs Duplicate command; +:Parse User Input; + +If () then ([Correct command format]) + :Duplicate the indicated recipe; +else ([Syntax error]) + :Returns failure message; +endif +:Displays duplicated recipe to user; + +stop +@enduml diff --git a/docs/diagrams/DuplicateSequenceDiagram.puml b/docs/diagrams/DuplicateSequenceDiagram.puml new file mode 100644 index 00000000000..3b8227a0363 --- /dev/null +++ b/docs/diagrams/DuplicateSequenceDiagram.puml @@ -0,0 +1,74 @@ +@startuml FindSequenceDiagram +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":RecipeBookParser" as RecipeBookParser LOGIC_COLOR +participant ":DuplicateCommandParser" as DuplicateCommandParser LOGIC_COLOR +participant ":DuplicateCommand" as DuplicateCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +participant ":ModifyCommand" as ModifyCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T2 +participant ":Model" as Model MODEL_COLOR +end box + +box Storage MODEL_COLOR_T1 +participant ":Storage" as Storage STORAGE_COLOR +end box + +[-> LogicManager : execute("duplicate 1") +activate LogicManager + +LogicManager -> RecipeBookParser : parseCommand("duplicate") +activate RecipeBookParser + +create DuplicateCommandParser +RecipeBookParser -> DuplicateCommandParser : parse("1") +activate DuplicateCommandParser + +DuplicateCommandParser --> RecipeBookParser : Command +deactivate DuplicateCommandParser +[<-[hidden]-DuplicateCommandParser +destroy DuplicateCommandParser + +RecipeBookParser --> LogicManager : Command +deactivate RecipeBookParser + +create DuplicateCommand +LogicManager -> DuplicateCommand : execute() +activate DuplicateCommand + +DuplicateCommand -> Model : updateFilteredRecipeList() +activate Model + +Model --> DuplicateCommand +deactivate Model + +DuplicateCommand -> ModifyCommand : EditRecipeDescriptor() +activate ModifyCommand + +ModifyCommand --> DuplicateCommand +deactivate ModifyCommand + +create CommandResult +DuplicateCommand -> CommandResult +activate CommandResult + +CommandResult --> DuplicateCommand : result +deactivate CommandResult + +DuplicateCommand --> LogicManager : result +deactivate DuplicateCommand +[<-[hidden]-DuplicateCommand +destroy DuplicateCommand + +LogicManager -> Storage : saveRecipeBook(recipeBook) +activate Storage + +Storage --> LogicManager +deactivate Storage + +[<--LogicManager : result +@enduml diff --git a/docs/diagrams/FavActivityDiagram.puml b/docs/diagrams/FavActivityDiagram.puml new file mode 100644 index 00000000000..815c1e75b7a --- /dev/null +++ b/docs/diagrams/FavActivityDiagram.puml @@ -0,0 +1,29 @@ +@startuml +!include style.puml + +start +:User inputs fav command; + +:Parser parses command; + +If () then ([Command is valid format]) + +If () then ([Recipe index exists]) + :Marks the recipe at specified index as favourited; + :Returns success message; +else ([else]) + :Returns failure message + (Invalid index); +endif +:LogicManager prints message to user; + +else ([else]) + :Returns failure message + (invalid command format); + +endif + +:Results are displayed to user; + +stop +@enduml diff --git a/docs/diagrams/FavSequenceDiagram.puml b/docs/diagrams/FavSequenceDiagram.puml new file mode 100644 index 00000000000..f377d049981 --- /dev/null +++ b/docs/diagrams/FavSequenceDiagram.puml @@ -0,0 +1,71 @@ +@startuml FavSequenceDiagram +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":RecipeBookParser" as RecipeBookParser LOGIC_COLOR +participant ":FavCommandParser" as FavCommandParser LOGIC_COLOR +participant "d:FavCommand" as FavCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + + +[-> LogicManager : execute("fav 1") +activate LogicManager + +LogicManager -> RecipeBookParser : parseCommand("fav 1") +activate RecipeBookParser + +create FavCommandParser +RecipeBookParser -> FavCommandParser +activate FavCommandParser + +FavCommandParser --> RecipeBookParser +deactivate FavCommandParser + +RecipeBookParser -> FavCommandParser : parse("1") +activate FavCommandParser + +create FavCommand +FavCommandParser -> FavCommand +activate FavCommand + +FavCommand --> FavCommandParser : d + +deactivate FavCommand + +FavCommandParser --> RecipeBookParser : d +deactivate FavCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +FavCommandParser -[hidden]-> RecipeBookParser +destroy FavCommandParser + +RecipeBookParser --> LogicManager : d +deactivate RecipeBookParser + +LogicManager -> FavCommand : execute() +activate FavCommand + +FavCommand -> Model : favRecipe(r) +activate Model + +Model --> FavCommand +deactivate Model + +create CommandResult +FavCommand -> CommandResult +activate CommandResult + +CommandResult --> FavCommand +deactivate CommandResult + +FavCommand --> LogicManager : result +deactivate FavCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/FindCommandActivityDiagram.puml b/docs/diagrams/FindCommandActivityDiagram.puml new file mode 100644 index 00000000000..c83d8d922d0 --- /dev/null +++ b/docs/diagrams/FindCommandActivityDiagram.puml @@ -0,0 +1,22 @@ +@startuml +!include style.puml + +start +:User enters input; +:Parse user input; + +if () then ([input contains only 1 prefix]) + :Break search string into single keywords; + if () then ([keywords exist]) + :Filter recipe book for recipes containing keywords; + :Update GUI with recipes found; + else ([else]) + :Throw "invalid search term" error message; + endif +else ([else]) + :Throw "incorrect number of prefixes detected" error message; +endif + +:Display results to user; +stop +@enduml diff --git a/docs/diagrams/FindCommandSequenceDiagram.puml b/docs/diagrams/FindCommandSequenceDiagram.puml new file mode 100644 index 00000000000..20e3a0f3bce --- /dev/null +++ b/docs/diagrams/FindCommandSequenceDiagram.puml @@ -0,0 +1,67 @@ +@startuml FindSequenceDiagram +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":RecipeBookParser" as RecipeBookParser LOGIC_COLOR +participant ":FindCommandParser" as FindCommandParser LOGIC_COLOR +participant ":FindCommand" as FindCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T2 +participant ":Model" as Model MODEL_COLOR +end box + +box Storage MODEL_COLOR_T1 +participant ":Storage" as Storage STORAGE_COLOR +end box + +[-> LogicManager : execute("find n/Ham") +activate LogicManager + +LogicManager -> RecipeBookParser : parseCommand("find") +activate RecipeBookParser + +create FindCommandParser +RecipeBookParser -> FindCommandParser : parse("n/Ham") +activate FindCommandParser + +FindCommandParser --> RecipeBookParser : Command +deactivate FindCommandParser +[<-[hidden]-FindCommandParser +destroy FindCommandParser + +RecipeBookParser --> LogicManager : Command +deactivate RecipeBookParser + +create FindCommand +LogicManager -> FindCommand : execute() +activate FindCommand + +FindCommand -> Model : updateFilteredRecipeList() +activate Model + +Model --> FindCommand +deactivate Model + +create CommandResult +FindCommand -> CommandResult +activate CommandResult + +CommandResult --> FindCommand : result +deactivate CommandResult + +FindCommand --> LogicManager : result +deactivate FindCommand +[<-[hidden]-FindCommand +destroy FindCommand + +LogicManager -> Storage : saveRecipeBook(recipeBook) +activate Storage + +Storage --> LogicManager +deactivate Storage + +[<--LogicManager : result +@enduml diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml index 016ef33e2e2..db5c37534e6 100644 --- a/docs/diagrams/LogicClassDiagram.puml +++ b/docs/diagrams/LogicClassDiagram.puml @@ -1,42 +1,41 @@ -@startuml +@startuml LogicClassDiagram !include style.puml skinparam arrowThickness 1.1 skinparam arrowColor LOGIC_COLOR_T4 skinparam classBackgroundColor LOGIC_COLOR -package Logic { - -package Parser { -Interface Parser <> -Class AddressBookParser -Class XYZCommandParser -Class CliSyntax -Class ParserUtil -Class ArgumentMultimap -Class ArgumentTokenizer -Class Prefix +package logic { + package parser { + Interface Parser <> + Class RecipeBookParser + Class XYZCommandParser + Class CliSyntax + Class ParserUtil + Class ArgumentMultimap + Class ArgumentTokenizer + Class Prefix + } + + package command { + Class XYZCommand + Class CommandResult + Class "{abstract}\nCommand" as Command + } + + Interface Logic <> + Class LogicManager } -package Command { -Class XYZCommand -Class CommandResult -Class "{abstract}\nCommand" as Command -} - -Interface Logic <> -Class LogicManager -} - -package Model{ -Class HiddenModel #FFFFFF +package model { + Class HiddenModel #FFFFFF } Class HiddenOutside #FFFFFF HiddenOutside ..> Logic LogicManager .up.|> Logic -LogicManager -->"1" AddressBookParser -AddressBookParser .left.> XYZCommandParser: creates > +LogicManager -->"1" RecipeBookParser +RecipeBookParser .left.> XYZCommandParser: creates > XYZCommandParser ..> XYZCommand : creates > XYZCommandParser ..|> Parser @@ -51,9 +50,9 @@ ArgumentTokenizer .down.> Prefix XYZCommand -up-|> Command LogicManager .left.> Command : executes > -LogicManager --> Model -Command .right.> Model -note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc +LogicManager --> model +Command -right-> model +note right of XYZCommand: XYZCommand = ""/new"" , ""/modify"", ""/list"", etc Logic ..> CommandResult LogicManager .down.> CommandResult diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index e85a00d4107..456b3ec070a 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -1,56 +1,45 @@ -@startuml +@startuml ModelClassDiagram !include style.puml skinparam arrowThickness 1.1 skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR -Package Model <>{ -Interface ReadOnlyAddressBook <> -Interface Model <> -Interface ObservableList <> -Class AddressBook -Class ReadOnlyAddressBook -Class Model -Class ModelManager -Class UserPrefs -Class ReadOnlyUserPrefs - -Package Person { -Class Person -Class Address -Class Email -Class Name -Class Phone -Class UniquePersonList -} +Package model { + Interface ReadOnlyRecipeBook <> + Interface Model <> + Interface ObservableList <> + Class RecipeBook + Class ReadOnlyRecipeBook + Class Model + Class ModelManager + Class UserPrefs + Class ReadOnlyUserPrefs -Package Tag { -Class Tag -} -} + Package recipe { + Class Recipe + Class UniqueRecipeList + } -Class HiddenOutside #FFFFFF -HiddenOutside ..> Model + Package attribute { + Class HiddenInside #FFFFFF + } -AddressBook .up.|> ReadOnlyAddressBook + Class HiddenOutside #FFFFFF + HiddenOutside ..> Model -ModelManager .up.|> Model -Model .right.> ObservableList -ModelManager o--> "1" AddressBook -ModelManager o-left-> "1" UserPrefs -UserPrefs .up.|> ReadOnlyUserPrefs + RecipeBook .up.|> ReadOnlyRecipeBook -AddressBook *--> "1" UniquePersonList -UniquePersonList o--> "*" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag + ModelManager .up.|> Model + Model .right.> ObservableList + ModelManager o--> "1" RecipeBook + ModelManager o-left-> "1" UserPrefs + UserPrefs .up.|> ReadOnlyUserPrefs -Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email + RecipeBook *--> "1" UniqueRecipeList + UniqueRecipeList o-down-> "*" Recipe -ModelManager -->"1" Person : filtered list -@enduml + Recipe -r-> HiddenInside + + ModelManager -->"1" Recipe : filtered list +} + @enduml diff --git a/docs/diagrams/PhotoReadActivityDiagram.puml b/docs/diagrams/PhotoReadActivityDiagram.puml new file mode 100644 index 00000000000..a880c46ecf2 --- /dev/null +++ b/docs/diagrams/PhotoReadActivityDiagram.puml @@ -0,0 +1,25 @@ +@startuml PhotoReadActivityDiagram + +!include style.puml + +start + +:User enters inputs; +:User input is parsed; + +if() then ([""p/"" prefix has arguments ""args""]) + :Attempt to read image file from ""args""; + if () then ([""args"" is a valid file path]) + :Read the file from ""args""; + :Return the image at ""args""; + else ([else]) + :Return the placeholder image; + endif +else ([else]) + :Return the placeholder image; +endif + +:Wrap the returned image in a ""Photograph""; +:Return the ""Photograph""; +stop +@enduml diff --git a/docs/diagrams/PhotoSaveActivityDiagram.puml b/docs/diagrams/PhotoSaveActivityDiagram.puml new file mode 100644 index 00000000000..197dfbda15a --- /dev/null +++ b/docs/diagrams/PhotoSaveActivityDiagram.puml @@ -0,0 +1,23 @@ +@startuml + +!include style.puml + +start + +:User inputs command; +:Recipe book attempts to save data; +:Recipe book attempts to save image data; + +if () then ([Image is the placeholder image]) + :Write ""placeholder"" to recipe data "".json"" file; +else ([else]) + if () then ([Image file already exists on disk]) + else ([else]) + :Write byte data from ""Photograph""'s ""BufferedImage"" \n attribute to a file, called ""recipename_hashcode.png""; + :Write image file name, ""recipename_hashcode.png"" \n to recipe data "".json"" file; + endif +endif + +stop + +@enduml diff --git a/docs/diagrams/PhotoSequenceDiagram.puml b/docs/diagrams/PhotoSequenceDiagram.puml new file mode 100644 index 00000000000..0d871c67889 --- /dev/null +++ b/docs/diagrams/PhotoSequenceDiagram.puml @@ -0,0 +1,65 @@ +@startuml PhotoSequenceDiagram +!include style.puml + +box logic LOGIC_COLOR_T1 +participant ":NewCommandParser" as NewCommandParser LOGIC_COLOR +participant ":ArgumentMultiMap" as ArgumentMultiMap LOGIC_COLOR + +end box + +box util STORAGE_COLOR_T1 +participant "<> \n :ParserUtil" as ParserUtil STORAGE_COLOR +participant "<>:ImageUtil" as ImageUtil STORAGE_COLOR +end box + +box model MODEL_COLOR_T1 +participant ":Photograph" as Photograph MODEL_COLOR +end box + +create NewCommandParser +[-> NewCommandParser : parse(""args"") +activate NewCommandParser + +create ArgumentMultiMap +NewCommandParser -> ArgumentMultiMap +activate ArgumentMultiMap +return argumentMultiMap + +NewCommandParser -> ParserUtil : parsePhotoFilePath(""arguments"") +activate ParserUtil + +ParserUtil -> ArgumentMultiMap : getValue(""\p"") +activate ArgumentMultiMap + +alt Argument parameters for ""p/"" exist + ArgumentMultiMap --> ParserUtil : ""arguments"" + deactivate ArgumentMultiMap + + ParserUtil -> ImageUtil : isPlaceHolderImage(""arguments"") + activate ImageUtil + + alt Is the placeholder image path + return ""PLACEHOLDER_IMAGE"" + NewCommandParser <-- ParserUtil : photograph + else Is not the placeholder image path + create Photograph + ParserUtil -> Photograph : Photograph(""path"") + activate Photograph + ParserUtil <-- Photograph + deactivate Photograph + NewCommandParser <-- ParserUtil : photograph + end +else No argument parameters + ParserUtil -> ArgumentMultiMap : orElse() + activate ArgumentMultiMap + ArgumentMultiMap -> ImageUtil : ""PLACEHOLDER_IMAGE"" + activate ImageUtil + return ""PLACEHOLDER_IMAGE"" + return ""PLACEHOLDER_IMAGE"" + return photograph +end + +[<-NewCommandParser +deactivate NewCommandParser + +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 6adb2e156bf..2af4531204b 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -1,4 +1,4 @@ -@startuml +@startuml StorageClassDiagram !include style.puml skinparam arrowThickness 1.1 skinparam arrowColor STORAGE_COLOR @@ -6,19 +6,19 @@ skinparam classBackgroundColor STORAGE_COLOR Interface Storage <> Interface UserPrefsStorage <> -Interface AddressBookStorage <> +Interface RecipeBookStorage <> Class StorageManager Class JsonUserPrefsStorage -Class JsonAddressBookStorage +Class JsonRecipeBookStorage StorageManager .left.|> Storage StorageManager o-right-> UserPrefsStorage -StorageManager o--> AddressBookStorage +StorageManager o--> RecipeBookStorage JsonUserPrefsStorage .left.|> UserPrefsStorage -JsonAddressBookStorage .left.|> AddressBookStorage -JsonAddressBookStorage .down.> JsonSerializableAddressBookStorage -JsonSerializableAddressBookStorage .right.> JsonSerializablePerson -JsonSerializablePerson .right.> JsonAdaptedTag +JsonRecipeBookStorage .left.|> RecipeBookStorage +JsonRecipeBookStorage .down.> JsonSerializableRecipeBookStorage +JsonSerializableRecipeBookStorage .right.> JsonSerializableRecipe +JsonSerializableRecipe .right.> JsonAdaptedTag @enduml diff --git a/docs/diagrams/TimeActivityDiagram.puml b/docs/diagrams/TimeActivityDiagram.puml new file mode 100644 index 00000000000..300b506254d --- /dev/null +++ b/docs/diagrams/TimeActivityDiagram.puml @@ -0,0 +1,31 @@ +@startuml +!include style.puml + +start +:User inputs time command; + +:Parser parses command; + +If () then ([Command is valid format]) + +If () then ([Recipe index exists]) + :Marks the recipe at specified index as attempted; + :Returns success message; +else ([else]) + :Returns failure message + (Invalid index); +endif +:LogicManager prints message to user; + +else ([else]) + + :Returns failure message + (invalid command format); + +endif + +:Results are displayed to user; + + +stop +@enduml diff --git a/docs/diagrams/TimeSequenceDiagram.puml b/docs/diagrams/TimeSequenceDiagram.puml new file mode 100644 index 00000000000..122c7bf142e --- /dev/null +++ b/docs/diagrams/TimeSequenceDiagram.puml @@ -0,0 +1,71 @@ +@startuml TimeSequenceDiagram +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":RecipeBookParser" as RecipeBookParser LOGIC_COLOR +participant ":TimeCommandParser" as TimeCommandParser LOGIC_COLOR +participant "d:TimeCommand" as TimeCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + + +[-> LogicManager : execute("time 1 01:00:00") +activate LogicManager + +LogicManager -> RecipeBookParser : parseCommand("time 1 01:00:00") +activate RecipeBookParser + +create TimeCommandParser +RecipeBookParser -> TimeCommandParser +activate TimeCommandParser + +TimeCommandParser --> RecipeBookParser +deactivate TimeCommandParser + +RecipeBookParser -> TimeCommandParser : parse("1 01:00:00") +activate TimeCommandParser + +create TimeCommand +TimeCommandParser -> TimeCommand +activate TimeCommand + +TimeCommand --> TimeCommandParser : d + +deactivate TimeCommand + +TimeCommandParser --> RecipeBookParser : d +deactivate TimeCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +TimeCommandParser -[hidden]-> RecipeBookParser +destroy TimeCommandParser + +RecipeBookParser --> LogicManager : d +deactivate RecipeBookParser + +LogicManager -> TimeCommand : execute() +activate TimeCommand + +TimeCommand -> Model : setTime(r, 01:00:00) +activate Model + +Model --> TimeCommand +deactivate Model + +create CommandResult +TimeCommand -> CommandResult +activate CommandResult + +CommandResult --> TimeCommand +deactivate CommandResult + +TimeCommand --> LogicManager : result +deactivate TimeCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 92746f9fcf7..7cf66ec3881 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -1,28 +1,28 @@ -@startuml +@startuml UiClassDiagram !include style.puml skinparam arrowThickness 1.1 skinparam arrowColor UI_COLOR_T4 skinparam classBackgroundColor UI_COLOR -package UI <>{ -Interface Ui <> -Class "{abstract}\nUiPart" as UiPart -Class UiManager -Class MainWindow -Class HelpWindow -Class ResultDisplay -Class PersonListPanel -Class PersonCard -Class StatusBarFooter -Class CommandBox +package UI { + Interface Ui <> + Class "{abstract}\nUiPart" as UiPart + Class UiManager + Class MainWindow + Class HelpWindow + Class ResultDisplay + Class RecipeListPanel + Class RecipeCard + Class StatusBarFooter + Class CommandBox } -package Model <> { -Class HiddenModel #FFFFFF +package Model { + Class HiddenModel #FFFFFF } -package Logic <> { -Class HiddenLogic #FFFFFF +package Logic { + Class HiddenLogic #FFFFFF } Class HiddenOutside #FFFFFF @@ -33,25 +33,25 @@ UiManager -down-> MainWindow MainWindow --> HelpWindow MainWindow *-down-> CommandBox MainWindow *-down-> ResultDisplay -MainWindow *-down-> PersonListPanel +MainWindow *-down-> RecipeListPanel MainWindow *-down-> StatusBarFooter -PersonListPanel -down-> PersonCard +RecipeListPanel -down-> RecipeCard MainWindow -left-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart +RecipeListPanel --|> UiPart +RecipeCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow -down-|> UiPart -PersonCard ..> Model +RecipeCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic -PersonListPanel -[hidden]left- HelpWindow +RecipeListPanel -[hidden]left- HelpWindow HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter diff --git a/docs/diagrams/UnFavSequenceDiagram.puml b/docs/diagrams/UnFavSequenceDiagram.puml new file mode 100644 index 00000000000..1a6e6168b2e --- /dev/null +++ b/docs/diagrams/UnFavSequenceDiagram.puml @@ -0,0 +1,71 @@ +@startuml UnfavSequenceDiagram +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":RecipeBookParser" as RecipeBookParser LOGIC_COLOR +participant ":UnfavCommandParser" as UnfavCommandParser LOGIC_COLOR +participant "d:UnfavCommand" as UnfavCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + + +[-> LogicManager : execute("unfav 1") +activate LogicManager + +LogicManager -> RecipeBookParser : parseCommand("unfav 1") +activate RecipeBookParser + +create UnfavCommandParser +RecipeBookParser -> UnfavCommandParser +activate UnfavCommandParser + +UnfavCommandParser --> RecipeBookParser +deactivate UnfavCommandParser + +RecipeBookParser -> UnfavCommandParser : parse("1") +activate UnfavCommandParser + +create UnfavCommand +UnfavCommandParser -> UnfavCommand +activate UnfavCommand + +UnfavCommand --> UnfavCommandParser : d + +deactivate UnfavCommand + +UnfavCommandParser --> RecipeBookParser : d +deactivate UnfavCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +UnfavCommandParser -[hidden]-> RecipeBookParser +destroy UnfavCommandParser + +RecipeBookParser --> LogicManager : d +deactivate RecipeBookParser + +LogicManager -> UnfavCommand : execute() +activate UnfavCommand + +UnfavCommand -> Model : unFavRecipe(r) +activate Model + +Model --> UnfavCommand +deactivate Model + +create CommandResult +UnfavCommand -> CommandResult +activate CommandResult + +CommandResult --> UnfavCommand +deactivate CommandResult + +UnfavCommand --> LogicManager : result +deactivate UnfavCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/UndoActivityDiagram.puml b/docs/diagrams/UndoActivityDiagram.puml new file mode 100644 index 00000000000..1db828731aa --- /dev/null +++ b/docs/diagrams/UndoActivityDiagram.puml @@ -0,0 +1,27 @@ +@startuml +!include style.puml + +start +:User inputs undo command; + +If () then ([Command is valid format]) + +If () then ([Recipe index exists]) + :Marks the recipe at specified index as not attempted; + :Returns success message; +else ([else]) + :Returns failure message + (Invalid index); +endif +:LogicManager prints message to user; + +else ([else]) + :Returns failure message + (invalid command format); + +endif + +:Results are displayed to user; + +stop +@enduml diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml deleted file mode 100644 index 96e30744d24..00000000000 --- a/docs/diagrams/UndoRedoState0.puml +++ /dev/null @@ -1,20 +0,0 @@ -@startuml -!include style.puml -skinparam ClassFontColor #000000 -skinparam ClassBorderColor #000000 - -title Initial state - -package States { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" -} -State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 -hide State2 -hide State3 - -class Pointer as "Current State" #FFFFF -Pointer -up-> State1 -@end diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml deleted file mode 100644 index 01fcb9b2b96..00000000000 --- a/docs/diagrams/UndoRedoState1.puml +++ /dev/null @@ -1,22 +0,0 @@ -@startuml -!include style.puml -skinparam ClassFontColor #000000 -skinparam ClassBorderColor #000000 - -title After command "delete 5" - -package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" -} - -State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 - -hide State3 - -class Pointer as "Current State" #FFFFF - -Pointer -up-> State2 -@end diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml deleted file mode 100644 index bccc230a5d1..00000000000 --- a/docs/diagrams/UndoRedoState2.puml +++ /dev/null @@ -1,20 +0,0 @@ -@startuml -!include style.puml -skinparam ClassFontColor #000000 -skinparam ClassBorderColor #000000 - -title After command "add n/David" - -package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" -} - -State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 - -class Pointer as "Current State" #FFFFF - -Pointer -up-> State3 -@end diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml deleted file mode 100644 index ea29c9483e4..00000000000 --- a/docs/diagrams/UndoRedoState3.puml +++ /dev/null @@ -1,20 +0,0 @@ -@startuml -!include style.puml -skinparam ClassFontColor #000000 -skinparam ClassBorderColor #000000 - -title After command "undo" - -package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" -} - -State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 - -class Pointer as "Current State" #FFFFF - -Pointer -up-> State2 -@end diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml deleted file mode 100644 index 1b784cece80..00000000000 --- a/docs/diagrams/UndoRedoState4.puml +++ /dev/null @@ -1,20 +0,0 @@ -@startuml -!include style.puml -skinparam ClassFontColor #000000 -skinparam ClassBorderColor #000000 - -title After command "list" - -package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab2:AddressBook__" -} - -State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 - -class Pointer as "Current State" #FFFFF - -Pointer -up-> State2 -@end diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml deleted file mode 100644 index 88927be32bc..00000000000 --- a/docs/diagrams/UndoRedoState5.puml +++ /dev/null @@ -1,21 +0,0 @@ -@startuml -!include style.puml -skinparam ClassFontColor #000000 -skinparam ClassBorderColor #000000 - -title After command "clear" - -package States <> { - class State1 as "__ab0:AddressBook__" - class State2 as "__ab1:AddressBook__" - class State3 as "__ab3:AddressBook__" -} - -State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 - -class Pointer as "Current State" #FFFFF - -Pointer -up-> State3 -note right on link: State ab2 deleted. -@end diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/UndoSequenceDiagram.puml index 410aab4e412..a37a5b8ea73 100644 --- a/docs/diagrams/UndoSequenceDiagram.puml +++ b/docs/diagrams/UndoSequenceDiagram.puml @@ -1,52 +1,70 @@ -@startuml +@startuml UndoSequenceDiagram !include style.puml box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR -participant "u:UndoCommand" as UndoCommand LOGIC_COLOR +participant ":RecipeBookParser" as RecipeBookParser LOGIC_COLOR +participant ":UndoCommandParser" as UndoCommandParser LOGIC_COLOR +participant "d:UndoCommand" as UndoCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR end box box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR -participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR end box -[-> LogicManager : execute(undo) + + +[-> LogicManager : execute("undo 1") activate LogicManager -LogicManager -> AddressBookParser : parseCommand(undo) -activate AddressBookParser +LogicManager -> RecipeBookParser : parseCommand("undo 1") +activate RecipeBookParser + +create UndoCommandParser +RecipeBookParser -> UndoCommandParser +activate UndoCommandParser + +UndoCommandParser --> RecipeBookParser +deactivate UndoCommandParser + +RecipeBookParser -> UndoCommandParser : parse("1") +activate UndoCommandParser create UndoCommand -AddressBookParser -> UndoCommand +UndoCommandParser -> UndoCommand activate UndoCommand -UndoCommand --> AddressBookParser +UndoCommand --> UndoCommandParser : d + deactivate UndoCommand -AddressBookParser --> LogicManager : u -deactivate AddressBookParser +UndoCommandParser --> RecipeBookParser : d +deactivate UndoCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +UndoCommandParser -[hidden]-> RecipeBookParser +destroy UndoCommandParser + +RecipeBookParser --> LogicManager : d +deactivate RecipeBookParser LogicManager -> UndoCommand : execute() activate UndoCommand -UndoCommand -> Model : undoAddressBook() +UndoCommand -> Model : unAttemptRecipe(r) activate Model -Model -> VersionedAddressBook : undo() -activate VersionedAddressBook - -VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook) -VersionedAddressBook --> Model : -deactivate VersionedAddressBook - Model --> UndoCommand deactivate Model +create CommandResult +UndoCommand -> CommandResult +activate CommandResult + +CommandResult --> UndoCommand +deactivate CommandResult + UndoCommand --> LogicManager : result deactivate UndoCommand -UndoCommand -[hidden]-> LogicManager : result -destroy UndoCommand [<--LogicManager deactivate LogicManager diff --git a/docs/diagrams/UnfavActivityDiagram.puml b/docs/diagrams/UnfavActivityDiagram.puml new file mode 100644 index 00000000000..e9706ad106a --- /dev/null +++ b/docs/diagrams/UnfavActivityDiagram.puml @@ -0,0 +1,29 @@ +@startuml +!include style.puml + +start +:User inputs unfav command; + +:Parser parses input; + +If () then ([Command is valid format]) + +If () then ([Recipe index exists]) + :Marks the recipe at specified index as not favourited + :Returns success message; +else ([else]) + :Returns failure message + (Invalid index); +endif +:LogicManager prints message to user; + +else ([else]) + :Returns failure message + (invalid command format); + +endif + +:Results are displayed to user; + +stop +@enduml diff --git a/docs/diagrams/ViewActivityDiagram.puml b/docs/diagrams/ViewActivityDiagram.puml new file mode 100644 index 00000000000..b786c092da6 --- /dev/null +++ b/docs/diagrams/ViewActivityDiagram.puml @@ -0,0 +1,19 @@ +@startuml +!include style.puml + +start +:User inputs view command; + +:Parser parses input; + +If () then ([Recipe index exists]) + :Updates the GUI to display the recipe at specified index; + :Returns success message; +else ([Recipe index does not exist]) + :Returns failure message + (Invalid index); +endif +:LogicManager prints message to user; + +stop +@enduml diff --git a/docs/diagrams/ViewSequenceDiagram.puml b/docs/diagrams/ViewSequenceDiagram.puml new file mode 100644 index 00000000000..77ca290d2f1 --- /dev/null +++ b/docs/diagrams/ViewSequenceDiagram.puml @@ -0,0 +1,79 @@ +@startuml ViewSequenceDiagram +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":RecipeBookParser" as RecipeBookParser LOGIC_COLOR +participant ":ViewCommandParser" as ViewCommandParser LOGIC_COLOR +participant "d:ViewCommand" as ViewCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +box Ui UI_COLOR_T1 +participant ":Ui" as Ui UI_COLOR +end box + +[-> LogicManager : execute("view 1") +activate LogicManager + +LogicManager -> RecipeBookParser : parseCommand("view 1") +activate RecipeBookParser + +create ViewCommandParser +RecipeBookParser -> ViewCommandParser +activate ViewCommandParser + +ViewCommandParser --> RecipeBookParser +deactivate ViewCommandParser + +RecipeBookParser -> ViewCommandParser : parse("1") +activate ViewCommandParser + +create ViewCommand +ViewCommandParser -> ViewCommand +activate ViewCommand + +ViewCommand --> ViewCommandParser : d +ViewCommand -> Ui: changeRecipe(r) +activate Ui + + +Ui --> ViewCommand +deactivate ViewCommand +deactivate Ui + +ViewCommandParser --> RecipeBookParser : d +deactivate ViewCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +ViewCommandParser -[hidden]-> RecipeBookParser +destroy ViewCommandParser + +RecipeBookParser --> LogicManager : d +deactivate RecipeBookParser + +LogicManager -> ViewCommand : execute() +activate ViewCommand + +ViewCommand -> Model : viewRecipe(r) +activate Model + +Model --> ViewCommand +deactivate Model + +create CommandResult +ViewCommand -> CommandResult +activate CommandResult + +CommandResult --> ViewCommand +deactivate CommandResult + +ViewCommand --> LogicManager : result +deactivate ViewCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/add-remark/CommandInterface.puml b/docs/diagrams/add-remark/CommandInterface.puml index 2c67afa598c..c0d7fb56e4d 100644 --- a/docs/diagrams/add-remark/CommandInterface.puml +++ b/docs/diagrams/add-remark/CommandInterface.puml @@ -1,4 +1,4 @@ -@startuml +@startuml CommandInterface !include ../style.puml Class "{abstract}\nCommand" as Command LOGIC_COLOR_T1{ @@ -31,4 +31,3 @@ hide circle show members hide CommandException members @enduml - diff --git a/docs/diagrams/add-remark/ParserInterface.puml b/docs/diagrams/add-remark/ParserInterface.puml index ff84b0881e4..7318d0d499a 100644 --- a/docs/diagrams/add-remark/ParserInterface.puml +++ b/docs/diagrams/add-remark/ParserInterface.puml @@ -1,4 +1,4 @@ -@startuml +@startuml ParserInterface !include ../style.puml Interface Parser LOGIC_COLOR_T1 @@ -16,4 +16,3 @@ show members skinparam classFontColor #000000 skinparam classAttributeIconSize 0 @enduml - diff --git a/docs/diagrams/plantuml/AbeforeC.puml b/docs/diagrams/plantuml/AbeforeC.puml index b4c86d69e08..9a64b8aa50b 100644 --- a/docs/diagrams/plantuml/AbeforeC.puml +++ b/docs/diagrams/plantuml/AbeforeC.puml @@ -1,4 +1,4 @@ -@startuml +@startuml ABeforeC !include ../style.puml Class A LOGIC_COLOR_T3 diff --git a/docs/diagrams/plantuml/AllDown.puml b/docs/diagrams/plantuml/AllDown.puml index 6eaf1c8bb00..2ed7766e39f 100644 --- a/docs/diagrams/plantuml/AllDown.puml +++ b/docs/diagrams/plantuml/AllDown.puml @@ -1,4 +1,4 @@ -@startuml +@startuml AllDown !include ../style.puml Class A LOGIC_COLOR_T3 diff --git a/docs/diagrams/plantuml/ArrowLength.puml b/docs/diagrams/plantuml/ArrowLength.puml index 99c5abfe062..186d5e43a3e 100644 --- a/docs/diagrams/plantuml/ArrowLength.puml +++ b/docs/diagrams/plantuml/ArrowLength.puml @@ -1,4 +1,4 @@ -@startuml +@startuml ArrowLength !include ../style.puml Package "Short\n->" { diff --git a/docs/diagrams/plantuml/CbeforeA.puml b/docs/diagrams/plantuml/CbeforeA.puml index 87dbca3f199..c2572fd5d4e 100644 --- a/docs/diagrams/plantuml/CbeforeA.puml +++ b/docs/diagrams/plantuml/CbeforeA.puml @@ -1,4 +1,4 @@ -@startuml +@startuml CBeforeA !include ../style.puml Class C UI_COLOR_T3 diff --git a/docs/diagrams/plantuml/HiddenArrows.puml b/docs/diagrams/plantuml/HiddenArrows.puml index c17ef6e4f13..a3656b11c66 100644 --- a/docs/diagrams/plantuml/HiddenArrows.puml +++ b/docs/diagrams/plantuml/HiddenArrows.puml @@ -1,4 +1,4 @@ -@startuml +@startuml HiddenArrows !include ../style.puml Class A LOGIC_COLOR_T3 diff --git a/docs/diagrams/plantuml/PackagesAndConsistency.puml b/docs/diagrams/plantuml/PackagesAndConsistency.puml index 6364eb5de03..2271ba1deb6 100644 --- a/docs/diagrams/plantuml/PackagesAndConsistency.puml +++ b/docs/diagrams/plantuml/PackagesAndConsistency.puml @@ -1,4 +1,4 @@ -@startuml +@startuml PackagesAndConsistency !include ../style.puml package "Rule Of Thumb"{ diff --git a/docs/diagrams/plantuml/UpAndDown.puml b/docs/diagrams/plantuml/UpAndDown.puml index e7a0313ad01..b3c7ddfa013 100644 --- a/docs/diagrams/plantuml/UpAndDown.puml +++ b/docs/diagrams/plantuml/UpAndDown.puml @@ -1,4 +1,4 @@ -@startuml +@startuml UpAndDown !include ../style.puml Class A LOGIC_COLOR_T3 diff --git a/docs/diagrams/style.puml b/docs/diagrams/style.puml index fad8b0adeaa..0bd9fffce61 100644 --- a/docs/diagrams/style.puml +++ b/docs/diagrams/style.puml @@ -42,13 +42,11 @@ skinparam Class { BorderThickness 1 BorderColor #FFFFFF StereotypeFontColor #FFFFFF - FontName Arial } skinparam Actor { BorderColor USER_COLOR Color USER_COLOR - FontName Arial } skinparam Sequence { @@ -56,7 +54,7 @@ skinparam Sequence { BoxFontSize 15 BoxPadding 0 BoxFontColor #FFFFFF - FontName Arial + SequenceBoxFontName Segoe UI } skinparam Participant { @@ -68,7 +66,10 @@ skinparam MinClassWidth 50 skinparam ParticipantPadding 10 skinparam Shadowing false skinparam DefaultTextAlignment center -skinparam packageStyle Rectangle +skinparam packageStyle Folder + +skinparam DefaultFontName Arial +skinparam defaultMonospacedFontName Consolas hide footbox hide members diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/tracing/LogicSequenceDiagram.puml index fdcbe1c0ccc..fab9adfdead 100644 --- a/docs/diagrams/tracing/LogicSequenceDiagram.puml +++ b/docs/diagrams/tracing/LogicSequenceDiagram.puml @@ -1,8 +1,8 @@ -@startuml +@startuml LogicSequenceDiagram !include ../style.puml Participant ":LogicManager" as logic LOGIC_COLOR -Participant ":AddressBookParser" as abp LOGIC_COLOR +Participant ":RecipeBookParser" as abp LOGIC_COLOR Participant ":EditCommandParser" as ecp LOGIC_COLOR Participant "command:EditCommand" as ec LOGIC_COLOR @@ -13,7 +13,7 @@ create ecp abp -> ecp abp -> ecp ++: parse(arguments) create ec -ecp -> ec ++: index, editPersonDescriptor +ecp -> ec ++: index, editRecipeDescriptor ec --> ecp -- ecp --> abp --: command abp --> logic --: command diff --git a/docs/images/ArchitectureDiagram.png b/docs/images/ArchitectureDiagram.png index aa2d337d932..eb74605b684 100644 Binary files a/docs/images/ArchitectureDiagram.png and b/docs/images/ArchitectureDiagram.png differ diff --git a/docs/images/ArchitectureDiagram.svg b/docs/images/ArchitectureDiagram.svg new file mode 100644 index 00000000000..00a4390caa1 --- /dev/null +++ b/docs/images/ArchitectureDiagram.svg @@ -0,0 +1,278 @@ +CookBuddyUILogicStorageModelMainCommonsLog CenterHiddenHiddenUIHiddenModel diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index aa198138f8f..3be1c0da3b3 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.svg b/docs/images/ArchitectureSequenceDiagram.svg new file mode 100644 index 00000000000..6c7bbffcb08 --- /dev/null +++ b/docs/images/ArchitectureSequenceDiagram.svg @@ -0,0 +1,97 @@ +User:UI:Logic:Model:Storage"delete 1"execute("delete 1")deleteRecipe(r)saveRecipeBook(recipeBook)Save to file diff --git a/docs/images/AttributeClassDiagram.svg b/docs/images/AttributeClassDiagram.svg new file mode 100644 index 00000000000..c807972a65f --- /dev/null +++ b/docs/images/AttributeClassDiagram.svg @@ -0,0 +1,143 @@ +attributerecipeNameIngredientListInstructionListIngredientInstructionCalorieDifficultyDoneFavPhotographRatingServingTagTimeRecipe1111..*1..*0..10..10..10..10..10..10..10..*0..1 diff --git a/docs/images/BasicRecipeDiagram.png b/docs/images/BasicRecipeDiagram.png new file mode 100644 index 00000000000..63ad3ffbb8d Binary files /dev/null and b/docs/images/BasicRecipeDiagram.png differ diff --git a/docs/images/BasicRecipeDiagram.svg b/docs/images/BasicRecipeDiagram.svg new file mode 100644 index 00000000000..2c760b27b12 --- /dev/null +++ b/docs/images/BasicRecipeDiagram.svg @@ -0,0 +1,169 @@ +RecipeBook-recipes : UniqueRecipeList+addRecipe()+deleteRecipe()+list()+duplicate()Recipe-name : String-ingredients : IngredientList-instructions : InstructionList-tags : TagList+modify()IngredientList-data : ArrayList<Ingredient>+add()+set()+remove()+list()Ingredient-name : String-quantity : QuantityIngredientStore-capacity : Integer-store : IngredientAvailabilityList+addIngredient()Freezer-temperature : DoubleRefrigerator-temperature : DoubleLarderInstructionList-data : HashMap<Instruction>+add()+edit()+list()+remove()Instruction-detail : StringTagList-data : ArrayList<Tag>+add()+remove()Tag-name : StringQuantity-amount : Float-unit : UnitUnit-name : String-symbol : UnitSymbol+convertTo()«enumeration»UnitSymboltsptbpcupmlgkglbfl ozIngredientAvailabilityList-availableIngredients : IngredientListboolean : isAvailable()contains1..*manyis a very cold & dryis a coldis a cool, dark & drystores0..*manycontains0..*manycontains1contains1contains1containsmany1..*contains1..*manyhas amountis measured inhas symbol*1 diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index bc7ed18ae29..c17c531dbbf 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/BetterModelClassDiagram.svg b/docs/images/BetterModelClassDiagram.svg new file mode 100644 index 00000000000..a869e19bab9 --- /dev/null +++ b/docs/images/BetterModelClassDiagram.svg @@ -0,0 +1,106 @@ +RecipeBookUniqueRecipeListUniqueTagListTagRecipeNameIngredientListInstructionListCalorieServingRating11** diff --git a/docs/images/CommitActivityDiagram.png b/docs/images/CommitActivityDiagram.png index 4de4fa4bf2b..336d8693f34 100644 Binary files a/docs/images/CommitActivityDiagram.png and b/docs/images/CommitActivityDiagram.png differ diff --git a/docs/images/CommitActivityDiagram.svg b/docs/images/CommitActivityDiagram.svg new file mode 100644 index 00000000000..5fafc2e0086 --- /dev/null +++ b/docs/images/CommitActivityDiagram.svg @@ -0,0 +1,25 @@ +User executes commandPurge redunant statesSave RecipeBook torecipeBookStateList[command commits RecipeBook][else] diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..babfb9b249f 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.svg b/docs/images/DeleteSequenceDiagram.svg new file mode 100644 index 00000000000..41741c6cc40 --- /dev/null +++ b/docs/images/DeleteSequenceDiagram.svg @@ -0,0 +1,128 @@ +LogicModel:LogicManager:RecipeBookParser:Modelexecute("delete 1")parseCommand("delete 1"):DeleteCommandParserparse("1")d:DeleteCommanddddexecute()deleteRecipe(1):CommandResultresult diff --git a/docs/images/DoneActivityDiagram.svg b/docs/images/DoneActivityDiagram.svg new file mode 100644 index 00000000000..3d5d1cfa39b --- /dev/null +++ b/docs/images/DoneActivityDiagram.svg @@ -0,0 +1,90 @@ +User inputs done commandParser parses input[Command is valid format][else][Recipe index exists][else]Marks the recipe at specified index as attemptedReturns success messageReturns failure message(Invalid index)LogicManager prints message to userReturns failure message(invalid command format)Results are displayed to user diff --git a/docs/images/DoneSequenceDiagram.svg b/docs/images/DoneSequenceDiagram.svg new file mode 100644 index 00000000000..d7787f23fb8 --- /dev/null +++ b/docs/images/DoneSequenceDiagram.svg @@ -0,0 +1,130 @@ +LogicModel:LogicManager:RecipeBookParser:Modelexecute("done 1")parseCommand("done 1"):DoneCommandParserparse("1")d:DoneCommanddddexecute()attemptRecipe(r):CommandResultresult diff --git a/docs/images/FavActivityDiagram.svg b/docs/images/FavActivityDiagram.svg new file mode 100644 index 00000000000..f537d190069 --- /dev/null +++ b/docs/images/FavActivityDiagram.svg @@ -0,0 +1,89 @@ +User inputs fav commandParser parses command[Command is valid format][else][Recipe index exists][else]Marks the recipe at specified index as favouritedReturns success messageReturns failure message(Invalid index)LogicManager prints message to userReturns failure message(invalid command format)Results are displayed to user diff --git a/docs/images/FavSequenceDiagram.svg b/docs/images/FavSequenceDiagram.svg new file mode 100644 index 00000000000..94eb410be35 --- /dev/null +++ b/docs/images/FavSequenceDiagram.svg @@ -0,0 +1,130 @@ +LogicModel:LogicManager:RecipeBookParser:Modelexecute("fav 1")parseCommand("fav 1"):FavCommandParserparse("1")d:FavCommanddddexecute()favRecipe(r):CommandResultresult diff --git a/docs/images/FindCommandActivityDiagram.png b/docs/images/FindCommandActivityDiagram.png new file mode 100644 index 00000000000..738d74ef05d Binary files /dev/null and b/docs/images/FindCommandActivityDiagram.png differ diff --git a/docs/images/FindCommandActivityDiagram.svg b/docs/images/FindCommandActivityDiagram.svg new file mode 100644 index 00000000000..7cd080bfdc7 --- /dev/null +++ b/docs/images/FindCommandActivityDiagram.svg @@ -0,0 +1,82 @@ +User enters inputParse user input[input contains only 1 prefix][else]Break search string into single keywords[keywords exist][else]Filter recipe book for recipes containing keywordsUpdate GUI with recipes foundThrow "invalid search term" error messageThrow "incorrect number of prefixes detected" error messageDisplay results to user diff --git a/docs/images/FindCommandSequenceDiagram.png b/docs/images/FindCommandSequenceDiagram.png new file mode 100644 index 00000000000..fd73143c7cb Binary files /dev/null and b/docs/images/FindCommandSequenceDiagram.png differ diff --git a/docs/images/FindCommandSequenceDiagram.svg b/docs/images/FindCommandSequenceDiagram.svg new file mode 100644 index 00000000000..13cb3022567 --- /dev/null +++ b/docs/images/FindCommandSequenceDiagram.svg @@ -0,0 +1,127 @@ +LogicModelStorage:LogicManager:RecipeBookParser:Model:Storageexecute("find n/Ham")parseCommand("find")parse("n/Ham"):FindCommandParserCommandCommandexecute():FindCommandupdateFilteredRecipeList():CommandResultresultresultsaveRecipeBook(recipeBook)result diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index b9e853cef12..46ae54fbe0a 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/LogicClassDiagram.svg b/docs/images/LogicClassDiagram.svg new file mode 100644 index 00000000000..e6c294f9a76 --- /dev/null +++ b/docs/images/LogicClassDiagram.svg @@ -0,0 +1,162 @@ +LogicParserCommandModel«Interface»LogicLogicManager«Interface»ParserRecipeBookParserXYZCommandParserCliSyntaxParserUtilArgumentMultimapArgumentTokenizerPrefixXYZCommandCommandResult{abstract}CommandHiddenModelHiddenOutsideXYZCommand = NewCommand,ModifyCommand, DeleteCommand etc1createscreatesexecutes diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 280064118cf..75467c3cbfb 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/ModelClassDiagram.svg b/docs/images/ModelClassDiagram.svg new file mode 100644 index 00000000000..a2779b7d497 --- /dev/null +++ b/docs/images/ModelClassDiagram.svg @@ -0,0 +1,131 @@ +modelrecipeattribute«Interface»ReadOnlyRecipeBook«Interface»Model«Interface»ObservableListRecipeBookModelManagerUserPrefsReadOnlyUserPrefsHiddenOutsideRecipeUniqueRecipeListHiddenInside111*filtered list1 diff --git a/docs/images/PhotoReadActivityDiagram.svg b/docs/images/PhotoReadActivityDiagram.svg new file mode 100644 index 00000000000..feb0e4ae488 --- /dev/null +++ b/docs/images/PhotoReadActivityDiagram.svg @@ -0,0 +1,85 @@ +User enters inputsUser input is parsed[p/prefix has argumentsargs][else]Attempt to read image file fromargs[argsis a valid file path][else]Read the file fromargsReturn the image atargsReturn the placeholder imageReturn the placeholder imageWrap the returned image in aPhotographReturn thePhotograph diff --git a/docs/images/PhotoSaveActivityDiagram.svg b/docs/images/PhotoSaveActivityDiagram.svg new file mode 100644 index 00000000000..98534a8ec90 --- /dev/null +++ b/docs/images/PhotoSaveActivityDiagram.svg @@ -0,0 +1,83 @@ +User inputs commandRecipe book attempts to save dataRecipe book attempts to save image data[Image is the placeholder image][else]Writeplaceholderto recipe data.jsonfileWrite byte data fromPhotograph'sBufferedImageattribute to a file, calledrecipename_hashcode.pngWrite image file name,recipename_hashcode.pngto recipe data.jsonfile[else][Image file already exists on disk] diff --git a/docs/images/PhotoSequenceDiagram.svg b/docs/images/PhotoSequenceDiagram.svg new file mode 100644 index 00000000000..1ff4638c011 --- /dev/null +++ b/docs/images/PhotoSequenceDiagram.svg @@ -0,0 +1,125 @@ +logicutilmodel:NewCommandParser<<class>>:ParserUtil<<class>>:ImageUtilparse(args):ArgumentMultiMapargumentMultiMapparsePhotoFilePath(arguments)getValue(\p)alt[Argument parameters forp/exist]argumentsisPlaceHolderImage(arguments)alt[Is the placeholder image path]PLACEHOLDER_IMAGEphotograph[Is not the placeholder image path]Photograph(path):Photographphotograph[No argument parameters]orElse()PLACEHOLDER_IMAGEPLACEHOLDER_IMAGEPLACEHOLDER_IMAGEphotograph diff --git a/docs/images/PhotographSequenceDiagram.svg b/docs/images/PhotographSequenceDiagram.svg new file mode 100644 index 00000000000..f18ce619f97 --- /dev/null +++ b/docs/images/PhotographSequenceDiagram.svg @@ -0,0 +1,115 @@ +LogicModel:LogicManager:RecipeBookParser:Model:VersionedRecipeBookexecute(undo)parseCommand(undo)u:UndoCommanduexecute()undoRecipeBook()undo()resetData(ReadOnlyRecipeBook)result diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index d87c1216820..1e017309fbe 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/StorageClassDiagram.svg b/docs/images/StorageClassDiagram.svg new file mode 100644 index 00000000000..1d7f8108291 --- /dev/null +++ b/docs/images/StorageClassDiagram.svg @@ -0,0 +1,101 @@ +«Interface»Storage«Interface»UserPrefsStorage«Interface»RecipeBookStorageStorageManagerJsonUserPrefsStorageJsonRecipeBookStorageJsonSerializableRecipeBookStorageJsonSerializableRecipeJsonAdaptedTag diff --git a/docs/images/TimeActivityDiagram.svg b/docs/images/TimeActivityDiagram.svg new file mode 100644 index 00000000000..638a57335d3 --- /dev/null +++ b/docs/images/TimeActivityDiagram.svg @@ -0,0 +1,91 @@ +User inputs time commandParser parses command[Command is valid format][else][Recipe index exists][else]Marks the recipe at specified index as attemptedReturns success messageReturns failure message(Invalid index)LogicManager prints message to userReturns failure message(invalid command format)Results are displayed to user diff --git a/docs/images/TimeSequenceDiagram.svg b/docs/images/TimeSequenceDiagram.svg new file mode 100644 index 00000000000..c78e3d004df --- /dev/null +++ b/docs/images/TimeSequenceDiagram.svg @@ -0,0 +1,130 @@ +LogicModel:LogicManager:RecipeBookParser:Modelexecute("time 1 01:00:00")parseCommand("time 1 01:00:00"):TimeCommandParserparse("1 01:00:00")d:TimeCommanddddexecute()setTime(r, 01:00:00):CommandResultresult diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..a18355b2eb6 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 7b4b3dbea45..77336f96e6e 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UiClassDiagram.svg b/docs/images/UiClassDiagram.svg new file mode 100644 index 00000000000..488f59dbfbd --- /dev/null +++ b/docs/images/UiClassDiagram.svg @@ -0,0 +1,160 @@ +UIModelLogic«Interface»Ui{abstract}UiPartUiManagerMainWindowHelpWindowResultDisplayRecipeListPanelRecipeCardStatusBarFooterCommandBoxHiddenModelHiddenLogicHiddenOutside diff --git a/docs/images/UndoActivityDiagram.svg b/docs/images/UndoActivityDiagram.svg new file mode 100644 index 00000000000..612b86679a1 --- /dev/null +++ b/docs/images/UndoActivityDiagram.svg @@ -0,0 +1,87 @@ +User inputs undo command[Command is valid format][else][Recipe index exists][else]Marks the recipe at specified index as not attemptedReturns success messageReturns failure message(Invalid index)LogicManager prints message to userReturns failure message(invalid command format)Results are displayed to user diff --git a/docs/images/UndoRedoState0.png b/docs/images/UndoRedoState0.png deleted file mode 100644 index 8f7538cd884..00000000000 Binary files a/docs/images/UndoRedoState0.png and /dev/null differ diff --git a/docs/images/UndoRedoState1.png b/docs/images/UndoRedoState1.png deleted file mode 100644 index df9908d0948..00000000000 Binary files a/docs/images/UndoRedoState1.png and /dev/null differ diff --git a/docs/images/UndoRedoState2.png b/docs/images/UndoRedoState2.png deleted file mode 100644 index 36519c1015b..00000000000 Binary files a/docs/images/UndoRedoState2.png and /dev/null differ diff --git a/docs/images/UndoRedoState3.png b/docs/images/UndoRedoState3.png deleted file mode 100644 index 19959d01712..00000000000 Binary files a/docs/images/UndoRedoState3.png and /dev/null differ diff --git a/docs/images/UndoRedoState4.png b/docs/images/UndoRedoState4.png deleted file mode 100644 index 4c623e4f2c5..00000000000 Binary files a/docs/images/UndoRedoState4.png and /dev/null differ diff --git a/docs/images/UndoRedoState5.png b/docs/images/UndoRedoState5.png deleted file mode 100644 index 84ad2afa6bd..00000000000 Binary files a/docs/images/UndoRedoState5.png and /dev/null differ diff --git a/docs/images/UndoSequenceDiagram.png b/docs/images/UndoSequenceDiagram.png index 6addcd3a8d9..e3276768a14 100644 Binary files a/docs/images/UndoSequenceDiagram.png and b/docs/images/UndoSequenceDiagram.png differ diff --git a/docs/images/UndoSequenceDiagram.svg b/docs/images/UndoSequenceDiagram.svg new file mode 100644 index 00000000000..a2f3b74ca34 --- /dev/null +++ b/docs/images/UndoSequenceDiagram.svg @@ -0,0 +1,130 @@ +LogicModel:LogicManager:RecipeBookParser:Modelexecute("undo 1")parseCommand("undo 1"):UndoCommandParserparse("1")d:UndoCommanddddexecute()unAttemptRecipe(r):CommandResultresult diff --git a/docs/images/UnfavActivityDiagram.svg b/docs/images/UnfavActivityDiagram.svg new file mode 100644 index 00000000000..fb2eef43715 --- /dev/null +++ b/docs/images/UnfavActivityDiagram.svg @@ -0,0 +1,89 @@ +User inputs unfav commandParser parses input[Command is valid format][else][Recipe index exists][else]Marks the recipe at specified index as not favourited:Returns success messageReturns failure message(Invalid index)LogicManager prints message to userReturns failure message(invalid command format)Results are displayed to user diff --git a/docs/images/UnfavSequenceDiagram.svg b/docs/images/UnfavSequenceDiagram.svg new file mode 100644 index 00000000000..ebe1c2ef881 --- /dev/null +++ b/docs/images/UnfavSequenceDiagram.svg @@ -0,0 +1,130 @@ +LogicModel:LogicManager:RecipeBookParser:Modelexecute("unfav 1")parseCommand("unfav 1"):UnfavCommandParserparse("1")d:UnfavCommanddddexecute()unFavRecipe(r):CommandResultresult diff --git a/docs/images/ViewActivityDiagram.svg b/docs/images/ViewActivityDiagram.svg new file mode 100644 index 00000000000..65b27c6bf93 --- /dev/null +++ b/docs/images/ViewActivityDiagram.svg @@ -0,0 +1,79 @@ +User inputs view commandParser parses input[Recipe index exists][Recipe index does not exist]Updates the GUI to display the recipe at specified indexReturns success messageReturns failure message(Invalid index)LogicManager prints message to user diff --git a/docs/images/ViewSequenceDiagram.svg b/docs/images/ViewSequenceDiagram.svg new file mode 100644 index 00000000000..8d0aacbd6a2 --- /dev/null +++ b/docs/images/ViewSequenceDiagram.svg @@ -0,0 +1,138 @@ +LogicModelUi:LogicManager:RecipeBookParser:Model:Uiexecute("view 1")parseCommand("view 1"):ViewCommandParserparse("1")d:ViewCommanddchangeRecipe(r)ddexecute()viewRecipe(r):CommandResultresult diff --git a/docs/images/adarshchugani.jpg b/docs/images/adarshchugani.jpg new file mode 100644 index 00000000000..15b08a58646 Binary files /dev/null and b/docs/images/adarshchugani.jpg differ diff --git a/docs/images/adarshchugani.png b/docs/images/adarshchugani.png new file mode 100644 index 00000000000..4e50a4f9cb2 Binary files /dev/null and b/docs/images/adarshchugani.png differ diff --git a/docs/images/add-remark/CommandInterface.png b/docs/images/add-remark/CommandInterface.png index b52e7811c52..81d2159c382 100644 Binary files a/docs/images/add-remark/CommandInterface.png and b/docs/images/add-remark/CommandInterface.png differ diff --git a/docs/images/add-remark/CommandInterface.svg b/docs/images/add-remark/CommandInterface.svg new file mode 100644 index 00000000000..f5984c1f0a1 --- /dev/null +++ b/docs/images/add-remark/CommandInterface.svg @@ -0,0 +1,98 @@ +{abstract}Command+execute(Model) : CommandResultCommandExceptionRemarkCommand+COMMAND_WORD : String+MESSAGE_USAGE : String+MESSAGE_NOT_IMPLEMENTED_YET: String                        +execute(Model) : CommandResultthrowsthrows diff --git a/docs/images/add-remark/ParserInterface.png b/docs/images/add-remark/ParserInterface.png index 60c7892a534..40b61f97a60 100644 Binary files a/docs/images/add-remark/ParserInterface.png and b/docs/images/add-remark/ParserInterface.png differ diff --git a/docs/images/add-remark/ParserInterface.svg b/docs/images/add-remark/ParserInterface.svg new file mode 100644 index 00000000000..8feb8465f08 --- /dev/null +++ b/docs/images/add-remark/ParserInterface.svg @@ -0,0 +1,83 @@ +ParserRemarkCommandParser+parse : RemarkCommandParserExceptionthrows diff --git a/docs/images/count-command/CountActivityDiagram.svg b/docs/images/count-command/CountActivityDiagram.svg new file mode 100644 index 00000000000..c84633ff909 --- /dev/null +++ b/docs/images/count-command/CountActivityDiagram.svg @@ -0,0 +1,77 @@ +User inputs Count command[command is valid format][else]Count all the recipesReturns success messageReturns failure message(Invalid Command)LogicManager prints message to user diff --git a/docs/images/count-command/CountSequenceDiagram.svg b/docs/images/count-command/CountSequenceDiagram.svg new file mode 100644 index 00000000000..da99babfc53 --- /dev/null +++ b/docs/images/count-command/CountSequenceDiagram.svg @@ -0,0 +1,111 @@ +LogicModel:LogicManager:RecipeBookParser:Modelexecute("count")parseCommand("count")d:CountCommandddexecute()count():CommandResultresult diff --git a/docs/images/damithc.jpg b/docs/images/damithc.jpg deleted file mode 100644 index 12754388389..00000000000 Binary files a/docs/images/damithc.jpg and /dev/null differ diff --git a/docs/images/e0316059.png b/docs/images/e0316059.png new file mode 100644 index 00000000000..562891c12b4 Binary files /dev/null and b/docs/images/e0316059.png differ diff --git a/docs/images/kevinswk94.png b/docs/images/kevinswk94.png new file mode 100644 index 00000000000..23f1ca64d1d Binary files /dev/null and b/docs/images/kevinswk94.png differ diff --git a/docs/images/lejolly.jpg b/docs/images/lejolly.jpg deleted file mode 100644 index 2d1d94e0cf5..00000000000 Binary files a/docs/images/lejolly.jpg and /dev/null differ diff --git a/docs/images/m133225.jpg b/docs/images/m133225.jpg deleted file mode 100644 index fd14fb94593..00000000000 Binary files a/docs/images/m133225.jpg and /dev/null differ diff --git a/docs/images/muhd97.png b/docs/images/muhd97.png new file mode 100644 index 00000000000..cb3736e7913 Binary files /dev/null and b/docs/images/muhd97.png differ diff --git a/docs/images/oldUI.PNG b/docs/images/oldUI.PNG new file mode 100644 index 00000000000..1027952c951 Binary files /dev/null and b/docs/images/oldUI.PNG differ diff --git a/docs/images/plantuml/ABeforeC.png b/docs/images/plantuml/ABeforeC.png index 39f28934ad5..e094f2d22d7 100644 Binary files a/docs/images/plantuml/ABeforeC.png and b/docs/images/plantuml/ABeforeC.png differ diff --git a/docs/images/plantuml/ABeforeC.svg b/docs/images/plantuml/ABeforeC.svg new file mode 100644 index 00000000000..27b860081f4 --- /dev/null +++ b/docs/images/plantuml/ABeforeC.svg @@ -0,0 +1,77 @@ +ABCD diff --git a/docs/images/plantuml/AllDown.png b/docs/images/plantuml/AllDown.png index a922ed70173..62bfa392013 100644 Binary files a/docs/images/plantuml/AllDown.png and b/docs/images/plantuml/AllDown.png differ diff --git a/docs/images/plantuml/AllDown.svg b/docs/images/plantuml/AllDown.svg new file mode 100644 index 00000000000..3f09f8f0caf --- /dev/null +++ b/docs/images/plantuml/AllDown.svg @@ -0,0 +1,102 @@ +ABCD1234Z diff --git a/docs/images/plantuml/ArrowLength.png b/docs/images/plantuml/ArrowLength.png index 01befd58ee5..d3ba4da1970 100644 Binary files a/docs/images/plantuml/ArrowLength.png and b/docs/images/plantuml/ArrowLength.png differ diff --git a/docs/images/plantuml/ArrowLength.svg b/docs/images/plantuml/ArrowLength.svg new file mode 100644 index 00000000000..3dd72f3299d --- /dev/null +++ b/docs/images/plantuml/ArrowLength.svg @@ -0,0 +1,103 @@ +Short->Long-->Longer--->Even Longer---->ABCDEFGH diff --git a/docs/images/plantuml/CBeforeA.png b/docs/images/plantuml/CBeforeA.png index b4157db03e8..7ffec5e5add 100644 Binary files a/docs/images/plantuml/CBeforeA.png and b/docs/images/plantuml/CBeforeA.png differ diff --git a/docs/images/plantuml/CBeforeA.svg b/docs/images/plantuml/CBeforeA.svg new file mode 100644 index 00000000000..8788a1ba7b3 --- /dev/null +++ b/docs/images/plantuml/CBeforeA.svg @@ -0,0 +1,77 @@ +CABD diff --git a/docs/images/plantuml/HiddenArrows.png b/docs/images/plantuml/HiddenArrows.png index 43c138a8bbf..b1af3130b87 100644 Binary files a/docs/images/plantuml/HiddenArrows.png and b/docs/images/plantuml/HiddenArrows.png differ diff --git a/docs/images/plantuml/HiddenArrows.svg b/docs/images/plantuml/HiddenArrows.svg new file mode 100644 index 00000000000..29c67296258 --- /dev/null +++ b/docs/images/plantuml/HiddenArrows.svg @@ -0,0 +1,109 @@ +ABCD1234Z diff --git a/docs/images/plantuml/PackagesAndConsistency.png b/docs/images/plantuml/PackagesAndConsistency.png index 28b9787d170..70c3d1c37a2 100644 Binary files a/docs/images/plantuml/PackagesAndConsistency.png and b/docs/images/plantuml/PackagesAndConsistency.png differ diff --git a/docs/images/plantuml/PackagesAndConsistency.svg b/docs/images/plantuml/PackagesAndConsistency.svg new file mode 100644 index 00000000000..3a54bffc1e1 --- /dev/null +++ b/docs/images/plantuml/PackagesAndConsistency.svg @@ -0,0 +1,80 @@ +Rule Of ThumbCDAB diff --git a/docs/images/plantuml/UpAndDown.png b/docs/images/plantuml/UpAndDown.png index b60d53a0258..f0ca5aea6db 100644 Binary files a/docs/images/plantuml/UpAndDown.png and b/docs/images/plantuml/UpAndDown.png differ diff --git a/docs/images/plantuml/UpAndDown.svg b/docs/images/plantuml/UpAndDown.svg new file mode 100644 index 00000000000..add909971ff --- /dev/null +++ b/docs/images/plantuml/UpAndDown.svg @@ -0,0 +1,102 @@ +ABCD1234Z diff --git a/docs/images/sharadhr.png b/docs/images/sharadhr.png new file mode 100644 index 00000000000..6d1beea0a00 Binary files /dev/null and b/docs/images/sharadhr.png differ diff --git a/docs/images/tracing/LogicSequenceDiagram.png b/docs/images/tracing/LogicSequenceDiagram.png index c9b1f6cc232..b3c0578598b 100644 Binary files a/docs/images/tracing/LogicSequenceDiagram.png and b/docs/images/tracing/LogicSequenceDiagram.png differ diff --git a/docs/images/tracing/LogicSequenceDiagram.svg b/docs/images/tracing/LogicSequenceDiagram.svg new file mode 100644 index 00000000000..5d67e7b8489 --- /dev/null +++ b/docs/images/tracing/LogicSequenceDiagram.svg @@ -0,0 +1,80 @@ +:LogicManager:RecipeBookParserexecuteparseCommand(commandText):EditCommandParserparse(arguments)index, editRecipeDescriptorcommand:EditCommandcommandcommand diff --git a/docs/images/user-guide/delete-after.png b/docs/images/user-guide/delete-after.png new file mode 100644 index 00000000000..ae240226216 Binary files /dev/null and b/docs/images/user-guide/delete-after.png differ diff --git a/docs/images/user-guide/delete-before.png b/docs/images/user-guide/delete-before.png new file mode 100644 index 00000000000..07dba0839d2 Binary files /dev/null and b/docs/images/user-guide/delete-before.png differ diff --git a/docs/images/user-guide/findIngredients-after.png b/docs/images/user-guide/findIngredients-after.png new file mode 100644 index 00000000000..9e8fedf4abf Binary files /dev/null and b/docs/images/user-guide/findIngredients-after.png differ diff --git a/docs/images/user-guide/findIngredients-before.png b/docs/images/user-guide/findIngredients-before.png new file mode 100644 index 00000000000..f18ef2e36bf Binary files /dev/null and b/docs/images/user-guide/findIngredients-before.png differ diff --git a/docs/images/user-guide/findInstructions-after.png b/docs/images/user-guide/findInstructions-after.png new file mode 100644 index 00000000000..c4be0ccf139 Binary files /dev/null and b/docs/images/user-guide/findInstructions-after.png differ diff --git a/docs/images/user-guide/findInstructions-before.png b/docs/images/user-guide/findInstructions-before.png new file mode 100644 index 00000000000..e877cbe2333 Binary files /dev/null and b/docs/images/user-guide/findInstructions-before.png differ diff --git a/docs/images/user-guide/findName-after.png b/docs/images/user-guide/findName-after.png new file mode 100644 index 00000000000..ca000942be2 Binary files /dev/null and b/docs/images/user-guide/findName-after.png differ diff --git a/docs/images/user-guide/findName-before.png b/docs/images/user-guide/findName-before.png new file mode 100644 index 00000000000..f2d55bd346b Binary files /dev/null and b/docs/images/user-guide/findName-before.png differ diff --git a/docs/images/user-guide/helpwindow.png b/docs/images/user-guide/helpwindow.png new file mode 100644 index 00000000000..a14df1029e1 Binary files /dev/null and b/docs/images/user-guide/helpwindow.png differ diff --git a/docs/images/user-guide/helpwindowdelete.png b/docs/images/user-guide/helpwindowdelete.png new file mode 100644 index 00000000000..e6125064776 Binary files /dev/null and b/docs/images/user-guide/helpwindowdelete.png differ diff --git a/docs/images/user-guide/modifyremovetag-after.png b/docs/images/user-guide/modifyremovetag-after.png new file mode 100644 index 00000000000..378f71aba77 Binary files /dev/null and b/docs/images/user-guide/modifyremovetag-after.png differ diff --git a/docs/images/user-guide/modifyremovetag-before.png b/docs/images/user-guide/modifyremovetag-before.png new file mode 100644 index 00000000000..29d64adf7bc Binary files /dev/null and b/docs/images/user-guide/modifyremovetag-before.png differ diff --git a/docs/images/user-guide/modifytag-after.png b/docs/images/user-guide/modifytag-after.png new file mode 100644 index 00000000000..d30de1a4e91 Binary files /dev/null and b/docs/images/user-guide/modifytag-after.png differ diff --git a/docs/images/user-guide/modifytag-before.png b/docs/images/user-guide/modifytag-before.png new file mode 100644 index 00000000000..50d928bafb0 Binary files /dev/null and b/docs/images/user-guide/modifytag-before.png differ diff --git a/docs/images/user-guide/new-after.png b/docs/images/user-guide/new-after.png new file mode 100644 index 00000000000..5fac1a44867 Binary files /dev/null and b/docs/images/user-guide/new-after.png differ diff --git a/docs/images/user-guide/new-before.png b/docs/images/user-guide/new-before.png new file mode 100644 index 00000000000..90d49beed35 Binary files /dev/null and b/docs/images/user-guide/new-before.png differ diff --git a/docs/images/user-guide/timecommand-after.png b/docs/images/user-guide/timecommand-after.png new file mode 100644 index 00000000000..29e8b98fcee Binary files /dev/null and b/docs/images/user-guide/timecommand-after.png differ diff --git a/docs/images/user-guide/timecommand-before.png b/docs/images/user-guide/timecommand-before.png new file mode 100644 index 00000000000..5fc0a4b1932 Binary files /dev/null and b/docs/images/user-guide/timecommand-before.png differ diff --git a/docs/images/user-guide/ui-components.png b/docs/images/user-guide/ui-components.png new file mode 100644 index 00000000000..0a00c0af1f2 Binary files /dev/null and b/docs/images/user-guide/ui-components.png differ diff --git a/docs/images/user-guide/view-after.png b/docs/images/user-guide/view-after.png new file mode 100644 index 00000000000..22c6caa9147 Binary files /dev/null and b/docs/images/user-guide/view-after.png differ diff --git a/docs/images/user-guide/view-before.png b/docs/images/user-guide/view-before.png new file mode 100644 index 00000000000..b496ed05974 Binary files /dev/null and b/docs/images/user-guide/view-before.png differ diff --git a/docs/images/yijinl.jpg b/docs/images/yijinl.jpg deleted file mode 100644 index adbf62ad940..00000000000 Binary files a/docs/images/yijinl.jpg and /dev/null differ diff --git a/docs/images/yl_coder.jpg b/docs/images/yl_coder.jpg deleted file mode 100644 index 17b48a73227..00000000000 Binary files a/docs/images/yl_coder.jpg and /dev/null differ diff --git a/docs/stylesheets/asciidoctor.css b/docs/stylesheets/asciidoctor.css index 36590bf346c..ec4d49560d5 100644 --- a/docs/stylesheets/asciidoctor.css +++ b/docs/stylesheets/asciidoctor.css @@ -42,7 +42,7 @@ textarea{overflow:auto;vertical-align:top} table{border-collapse:collapse;border-spacing:0} *,*:before,*:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box} html,body{font-size:100%} -body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto} +body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family: -apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Open Sans", "Liberation Sans", sans-serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto} a:hover{cursor:pointer} img,object,embed{max-width:100%;height:auto} object,embed{height:100%} @@ -68,8 +68,8 @@ a:hover,a:focus{color:#1d4b8f} a img{border:none} p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility} p aside{font-size:.875em;line-height:1.35;font-style:italic} -h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em} -h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Open Sans","Liberation Sans",sans-serif;font-weight:100;font-style:normal;color:#531300;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em} +h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#62413c;line-height:0} h1{font-size:2.125em} h2{font-size:1.6875em} h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em} diff --git a/docs/team/adarshchugani.adoc b/docs/team/adarshchugani.adoc new file mode 100644 index 00000000000..499652b6293 --- /dev/null +++ b/docs/team/adarshchugani.adoc @@ -0,0 +1,82 @@ += Adarsh Chugani - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: CookBuddy + +--- + +== Overview + +*CookBuddy* is a desktop recipe manager for students staying in university accommodation who enjoy cooking. +They can interact with it through a Command Line Interface (CLI), and it has a Graphical User Interface (GUI) created with JavaFX. +It is written in Java, and has about 10 kLoC. + +== Summary of contributions + +* *Major enhancement*: added *the ability to favourite/un-favourite recipes* +** What it does: allows the user to favourite or un-favourite a recipe. A recipe is marked as not favourited by +default, but the user can change this using the `fav` command. If a user wants to un-favourite a recipe, they may do +so using the `unfav` command. + +* *Major enhancement #2*: implemented the `view` command +** What it does: allows users to view any recipe on the GUI. Before this feature was implemented, the displayed +recipe would always be the first recipe added in the application. + +* *Minor enhancement*: Added the `done` and `undo` features, which behave similarly to the `fav` and `unfav` commands. +They are used to mark recipes as attempted and not attempted, respoectively. + +* *Minor enhancement #2*: implemented the `time` command +** What it does: allows users to assign a time to a recipe. Users may exclude hours and seconds when inputting the +time, and these fields will be set to 0 if they are omitted. + +* *Code contributed*: [https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=AdarshChugani&sort=groupTitle&sortWithin=title&since=2020-02-14&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false[Code Contribution]] + +* *Other contributions*: + +** Project management: +*** Managed releases `v1.1` - `v1.4` (4 releases) on GitHub +** Enhancements to existing features: +*** Updated the GUI color scheme https://github.com/AY1920S2-CS2103T-W12-4/main/issues/71[Pull Request #71] +*** Wrote additional tests for existing features to increase coverage from 27% to 51%: Pull requests https://github.com/AY1920S2-CS2103T-W12-4/main/issues/299[#299], https://github.com/AY1920S2-CS2103T-W12-4/main/issues/300[#300] +** Documentation: +*** Did many changes to the User Guide and Developer Guide: Pull Requests +https://github.com/AY1920S2-CS2103T-W12-4/main/pull/20[#20], https://github.com/AY1920S2-CS2103T-W12-4/main/pull/22[#22], +https://github.com/AY1920S2-CS2103T-W12-4/main/pull/89[#89], https://github.com/AY1920S2-CS2103T-W12-4/main/pull/193[#193], https://github.com/AY1920S2-CS2103T-W12-4/main/pull/327[#327] as well as many more. +** Community: +*** Approved many team-members PRs, often providing feedback if it did not meet standards. +*** Contributed to forum discussions +*** Created the vast majority of the issues on the Issue Tracker +https://github.com/AY1920S2-CS2103T-W12-4/main/issues/[here] +** Tools: +*** Integrated Codacy as well as Coveralls. + + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + + +//include::../UserGuide.adoc[tag=TimeCommand] +include::../UserGuide.adoc[tag=FavCommand] +include::../UserGuide.adoc[tag=FutureStuff] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + + +include::../DeveloperGuide.adoc[tag=FavCommandImpl] +include::../DeveloperGuide.adoc[tag=TimeCommandImpl] +include::../DeveloperGuide.adoc[tag=ViewCommandImpl] +//include::../DeveloperGuide.adoc[tag=UserStories] + + +== PROJECT: PowerPointLabs + diff --git a/docs/team/e0316059.adoc b/docs/team/e0316059.adoc new file mode 100644 index 00000000000..9db3ac9014f --- /dev/null +++ b/docs/team/e0316059.adoc @@ -0,0 +1,65 @@ += Mingsi - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: CookBuddy + +--- + +== Overview + +*CookBuddy* is a desktop recipe manager for students staying in university accommodation who enjoy cooking. +They can interact with it through a Command Line Interface (CLI), and it has a Graphical User Interface (GUI) created with JavaFX. +It is written in Java, and has about 10 kLoC. + +== Summary of contributions + +* *Major enhancement*: added *the ability to duplicate a recipe* +** What it does: Generally the application does not allow duplicated recipe (recipes with the same names, ingredients and instructions are considered as the same). By adding duplicate command, a new recipe with the same content but a new name with perfix 'Duplicate of' can be added to the recipe list. +** Justification: This feature improves the product because a user can easily make two similar recipes by making small modification after duplication, instead of typing the whole content again. +** Highlights: The implementation allows the user to add duplicated recipe while pass the 'identical recipe' checking. + +* *Minor enhancement*: added a scale recipe command that allows the user to scale their recipes by a given size. + +* *Code contributed*: [https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=e0316059&sort=groupTitle&sortWithin=title&since=2020-02-14&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false[Code Contribution]] + +* *Other contributions*: + +** Project management: +*** Participate in group discussion +** Enhancements to existing features: +*** Added a method to convert the ingredient of a recipe from string to readable quantity +*** Added command to mark a recipe as done +** Community: +*** Reported bugs and suggestions for other teams in the class (examples: https://github.com/e0316059/ped) + +_{you can add/remove categories in the list above}_ + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=delete] + +include::../UserGuide.adoc[tag=dataencryption] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=undoredo] + +include::../DeveloperGuide.adoc[tag=dataencryption] + + +== PROJECT: PowerPointLabs + +--- + +_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/kevinswk94.adoc b/docs/team/kevinswk94.adoc new file mode 100644 index 00000000000..62a31f3df0d --- /dev/null +++ b/docs/team/kevinswk94.adoc @@ -0,0 +1,82 @@ += Kevin - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: CookBuddy + + +== Overview + +*CookBuddy* is a desktop recipe manager for students staying in university accommodation who enjoy cooking. +They can interact with it through a Command Line Interface (CLI), and it has a Graphical User Interface (GUI) created with JavaFX. +It is written in Java, and has about 10 kLoC. + +== Summary of contributions + +* *Major enhancement*: Morphed addressbook-level3 into a basic version of *CookBuddy*. https://github.com/AY1920S2-CS2103T-W12-4/main/pull/75[#75] +** What it does: allows my teammates to start working on features to extend *CookBuddy*. +** Justification: *CookBuddy* does not make use of the Person class or its fields like _Email_ & _Address_. +** Highlights: This enhancement affects the commands *CookBuddy* can respond to. For instance, *CookBuddy* responds to `new` instead of `add`. + +* *Major enhancement*: Created the test framework for *CookBuddy*. https://github.com/AY1920S2-CS2103T-W12-4/main/pull/120[#120] +** What is does: It creates the test framework for my teammates to create test classes for *CookBuddy*. +** Justification: This enhancement makes it a bit easier for my teammates to write test classes for *CookBuddy*. +** Highlights: The test framework has some sample recipes with initialised attributes to make creating test recipes a bit easier. + +* *Minor enhancement*: added a Command History feature that allows the user to navigate to previous commands using the page up / page down keys. +https://github.com/AY1920S2-CS2103T-W12-4/main/pull/289[#289] + +* *Code contributed*: [https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=kevinswk94&sort=groupTitle&sortWithin=title&since=2020-02-14&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=kevinswk94&tabRepo=AY1920S2-CS2103T-W12-4%2Fmain%5Bmaster%5D[Code Contributed]] + +* *Other contributions*: + +** Project management: +*** Reviewed pull requests opened by teammates for code quality and bugs (https://github.com/AY1920S2-CS2103T-W12-4/main/pull/133[#133], +https://github.com/AY1920S2-CS2103T-W12-4/main/pull/127[#127], https://github.com/AY1920S2-CS2103T-W12-4/main/pull/257[#257]) + +** Enhancements to existing features: +*** Refactored the _AddCommand_ command into _NewCommand_. https://github.com/AY1920S2-CS2103T-W12-4/main/pull/77[#77] +*** Added a command history to the _Command Box_ so that users can reuse previously entered commands. https://github.com/AY1920S2-CS2103T-W12-4/main/pull/289[#289] +*** Updated tag parsing from multiple t/ prefixes to a single t/ prefix with comma delimited tags. https://github.com/AY1920S2-CS2103T-W12-4/main/pull/255[#255] + +** Documentation: +*** Updated the instructions for the `new` and `modify` commands in the User Guide. https://github.com/AY1920S2-CS2103T-W12-4/main/pull/170[#170] +*** Updated the instructions for the `find` command in the User Guide. https://github.com/AY1920S2-CS2103T-W12-4/main/pull/314[#314], + https://github.com/AY1920S2-CS2103T-W12-4/main/pull/164[#164] +*** Added instructions on how to use the `find` command in the User Guide. https://github.com/AY1920S2-CS2103T-W12-4/main/pull/164/[#164] +*** Added an explanation of how the `find` command works to the Developer Guide. https://github.com/AY1920S2-CS2103T-W12-4/main/pull/323[#323] +*** Added Instructions for Manual Testing to the Developer Guide. https://github.com/AY1920S2-CS2103T-W12-4/main/pull/302[#302] + +** Community: +*** PRs reviewed (with non-trivial review comments): +https://github.com/AY1920S2-CS2103T-W12-4/main/pull/295[#295], +https://github.com/AY1920S2-CS2103T-W12-4/main/pull/293[#293], +https://github.com/AY1920S2-CS2103T-W12-4/main/pull/171[#171], +https://github.com/AY1920S2-CS2103T-W12-4/main/pull/124[#124], +https://github.com/AY1920S2-CS2103T-W12-4/main/pull/113[#113] + +*** Reported bugs and suggestions for other teams during the Practical Exam Dry-Run + (examples: https://github.com/kevinswk94/ped/issues/4[1], https://github.com/kevinswk94/ped/issues/1[2], https://github.com/kevinswk94/ped/issues/6[3]) + + +== Contributions to the User Guide + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=AddRecipe] + +include::../UserGuide.adoc[tag=FindRecipe] + + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=FindCommandImpl] + +include::../DeveloperGuide.adoc[tag=ManualTesting] diff --git a/docs/team/muhd97.adoc b/docs/team/muhd97.adoc new file mode 100644 index 00000000000..6051315e001 --- /dev/null +++ b/docs/team/muhd97.adoc @@ -0,0 +1,75 @@ += Zain Alam - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: CookBuddy + +--- + +== Overview + +*CookBuddy* is a personal desktop recipe manager application for students staying in university accommodation who enjoy cooking. +The user can interact with it through a Command Line Interface (CLI) along with a Graphical User Interface (GUI) created with JavaFX. +It is written in Java and only supports Java 11. It also has close to 10 kLoC. With that being said, there are future plans to expand +the application to an online version as well as for different platforms. + +== Summary of contributions + +* *Major enhancement*: +. Added a count command that allows the user to count the total number of recipes stored in CookBuddy. +** What it does: It gives the user an ability to keep track of total number of recipes stored in CookBuddy. +** Justification: This feature improves the product significantly because a user can know the number of recipes he/ she has. When a user have highly differentiated tastes and needs, knowing how many recipes he/ she has can let him/ her satisfy his/ her own particular wants. +** Highlights: The user is able to view the number of recipes he/ she has. This enhancement also affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + +* *Minor enhancement*: +. Added a calorie attribute to a recipe class with the necessary restraints. + +* *Code contributed*: [https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=muhd97&sort=groupTitle&sortWithin=title&since=2020-02-14&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false[Functional code and Test code contributed]] + +* *Other contributions*: + +** Project management: +*** Helped in managing the team's repository and pull requests. Commented on other's PR if there are any improvements or suggestions to make. +*** Helped teammates with testing errors. +*** Helped in the documentations of the overall code. +*** Explained to teammates the flow of AB3 to better help them understand the code and therefore aiding them in implementing their own features. +** Enhancements to existing features: +*** Wrote additional tests for existing features to increase coverage (Pull requests https://github.com/AY1920S2-CS2103T-W12-4/main/pull/301[#301], https://github.com/AY1920S2-CS2103T-W12-4/main/pull/306[#306]) +*** Major refactoring of certain commands and packaging (Pull requests https://github.com/AY1920S2-CS2103T-W12-4/main/pull/17[#17], https://github.com/AY1920S2-CS2103T-W12-4/main/pull/74[#74], https://github.com/AY1920S2-CS2103T-W12-4/main/pull/147[#147], https://github.com/AY1920S2-CS2103T-W12-4/main/pull/154[#154]) +** Documentation: +*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com/AY1920S2-CS2103T-W12-4/main/pull/295[#295] +*** Updated User Guide to include Product Information, Understanding CookBuddy's GUI, FAQs, Command Summary and the Glossary (Pull requests https://github.com/AY1920S2-CS2103T-W12-4/main/pull/171[#171], https://github.com/AY1920S2-CS2103T-W12-4/main/pull/279[#279], https://github.com/AY1920S2-CS2103T-W12-4/main/pull/295[#295], https://github.com/AY1920S2-CS2103T-W12-4/main/pull/298[#298]) +*** Updated Developer Guide to include Introduction, About this Document, Overview of Features, Implementation details of Count feature, Value Proposition, Non Functional Requirements, Glossary and Product Survey (Pull requests https://github.com/AY1920S2-CS2103T-W12-4/main/pull/297[#297], https://github.com/AY1920S2-CS2103T-W12-4/main/pull/305[#305], https://github.com/AY1920S2-CS2103T-W12-4/main/pull/313[#313]) +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com/AY1920S2-CS2103T-W12-4/main/pull/113[#113], https://github.com/AY1920S2-CS2103T-W12-4/main/pull/114[#114], https://github.com/AY1920S2-CS2103T-W12-4/main/pull/125[#125], https://github.com/AY1920S2-CS2103T-W12-4/main/pull/170[#170] +*** Reported bugs and suggestions for other teams (examples: https://github.com/muhd97/ped[Practical Exam Dry Run] + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=productinformation] + +include::../UserGuide.adoc[tag=understandingcookBuddysgui] + +include::../UserGuide.adoc[tag=faqcmdsummaryglossary] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=introductionabouththisdocumentoverviewoffeatures] + +include::../DeveloperGuide.adoc[tag=countfeature] + +include::../DeveloperGuide.adoc[tag=productscope] + +include::../DeveloperGuide.adoc[tag=nonfuncreqglossarypdtsurvey] + + diff --git a/docs/team/johndoe.adoc b/docs/team/sharadhr.adoc similarity index 56% rename from docs/team/johndoe.adoc rename to docs/team/sharadhr.adoc index f39e76e49b2..d0c0a61f7d9 100644 --- a/docs/team/johndoe.adoc +++ b/docs/team/sharadhr.adoc @@ -1,27 +1,31 @@ -= John Doe - Project Portfolio += Sharadh Rajaraman — Project Portfolio :site-section: AboutUs :imagesDir: ../images :stylesDir: ../stylesheets -== PROJECT: AddressBook - Level 3 +== PROJECT: CookBuddy --- == Overview -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. +*CookBuddy* is a desktop recipe manager for students living in on-campus accommodation, and who enjoy cooking, and are also familiar with the command line. + +Users may interact with CookBuddy through a Command Line Interface (CLI), and it has a Graphical User Interface (GUI) created with JavaFX. + +CookBuddy is written in Java, and has about 10 kLoC. == Summary of contributions -* *Major enhancement*: added *the ability to undo/redo previous commands* -** What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. -** Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. +* *Major enhancement*: added *Image reading, processing and writing* +** What it does: allows users to add images to their recipes. Images are saved in a data folder local to CookBuddy, and are named with a UID (unique identifier). +** Justification: Users should be able to see what the final dish looks like, and an image goes ** Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. ** Credits: _{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}_ * *Minor enhancement*: added a history command that allows the user to navigate to previous commands using up/down keys. -* *Code contributed*: [https://github.com[Functional code]] [https://github.com[Test code]] _{give links to collated code files}_ +* *Code contributed*: [https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=&sort=groupTitle&sortWithin=title&since=2020-02-14&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=sharadhr&tabRepo=AY1920S2-CS2103T-W12-4%2Fmain%5Bmaster%5D[Code Contributed]] * *Other contributions*: @@ -42,31 +46,3 @@ AddressBook - Level 3 is a desktop address book application used for teaching So *** Integrated a new Github plugin (CircleCI) to the team repo _{you can add/remove categories in the list above}_ - -== Contributions to the User Guide - - -|=== -|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ -|=== - -include::../UserGuide.adoc[tag=delete] - -include::../UserGuide.adoc[tag=dataencryption] - -== Contributions to the Developer Guide - -|=== -|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ -|=== - -include::../DeveloperGuide.adoc[tag=undoredo] - -include::../DeveloperGuide.adoc[tag=dataencryption] - - -== PROJECT: PowerPointLabs - ---- - -_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/sharadhr/[CS2103T-W12-4][Sharadh Rajaraman]PPP.pdf b/docs/team/sharadhr/[CS2103T-W12-4][Sharadh Rajaraman]PPP.pdf new file mode 100644 index 00000000000..99d605b0437 Binary files /dev/null and b/docs/team/sharadhr/[CS2103T-W12-4][Sharadh Rajaraman]PPP.pdf differ diff --git a/docs/team/sharadhr/preamble.tex b/docs/team/sharadhr/preamble.tex new file mode 100644 index 00000000000..bbbfc128c0f --- /dev/null +++ b/docs/team/sharadhr/preamble.tex @@ -0,0 +1,182 @@ +\usepackage[a4paper,margin=2cm]{geometry} +\usepackage[utf8]{inputenc} +\usepackage{inconsolata} +\usepackage{MnSymbol} +\usepackage[ + minionint, + lf, + mathtabular, + loosequotes, + swash, + opticals]{MinionPro} +\usepackage{amsmath}%, amsfonts, amssymb} +% \usepackage{wasysym} +\usepackage[useregional,calc]{datetime2} +\usepackage[super]{nth} +\input{glyphtounicode} +\pdfgentounicode=1 +\pdfminorversion=7 + +\usepackage[ + arc-separator = \,, + retain-explicit-plus, + detect-weight=true, + detect-family=true, + separate-uncertainty=true, + multi-part-units=brackets]{siunitx} +\usepackage[version=4]{mhchem} +\usepackage[ISO]{diffcoeff} +\usepackage{bm} +\usepackage{esvect} + +\usepackage{enumitem} +\usepackage{parskip} +\usepackage{multicol} +\usepackage{titlesec} +\usepackage{microtype} +\usepackage{bigfoot} + +\usepackage{tabularx} +\usepackage{booktabs} + +%\usepackage{listings} +\usepackage[usenames,dvipsnames]{xcolor} +\usepackage{pgfplots,pgfplotstable} +%\usepackage[skins,theorems]{tcolorbox} +\usepackage{graphicx} +\usepackage{epstopdf} +\usepackage[labelfont={small,bf},font={small}]{caption} +\usepackage{subcaption} +\usepackage{float} +%\tcbset{shield externalize,highlight math style={enhanced, +% colframe=red,colback=white,arc=0pt,boxrule=1pt}} +\pgfplotsset{compat=newest} +\usetikzlibrary{ + shapes,shapes.misc, + arrows,arrows.meta, + calc,positioning, + decorations.pathreplacing,decorations.markings, + decorations.text,calligraphy, + pgfplots.dateplot, + optics,external, + circuits.ee.IEC +} +\tikzset{ + on each segment/.style={ + decorate, + decoration={ + show path construction, + moveto code={}, + lineto code={ + \path [#1] + (\tikzinputsegmentfirst) -- (\tikzinputsegmentlast); + }, + curveto code={ + \path [#1] (\tikzinputsegmentfirst) + .. controls + (\tikzinputsegmentsupporta) and (\tikzinputsegmentsupportb) + .. + (\tikzinputsegmentlast); + }, + closepath code={ + \path [#1] + (\tikzinputsegmentfirst) -- (\tikzinputsegmentlast); + }, + }, + }, + mid arrow/.style={postaction={decorate,decoration={ + markings, + mark=at position .5 with {\arrow[#1]{Stealth}} + }}}, + >=Stealth +} +\newcommand*\circled[1]{\tikz[baseline=(char.base)]{% + \node[shape=circle, draw, minimum size=1.25em, inner sep=0pt, thick] (char) {#1};}} +\tikzexternalize[prefix=figures/] +\captionsetup{width=0.6\textwidth} + +\usepackage[nottoc,numbib]{tocbibind} +\usepackage[ + backend=biber, + language=british, + backref=true, + style=verbose-ieee, + bibstyle=numeric, + citestyle=numeric, + sorting=none +]{biblatex} + +%\addbibresource{'hall_effect_report.bib'} + +\DefineBibliographyStrings{english}{% + backrefpage = {page},% originally "cited on page" + backrefpages = {pages},% originally "cited on pages" +} +\DeclareCiteCommand{\supercite}[\mkbibsuperscript] +{\iffieldundef{prenote} + {} + {\BibliographyWarning{Ignoring prenote argument}}% + \iffieldundef{postnote} + {} + {\BibliographyWarning{Ignoring postnote argument}}} +{\usebibmacro{citeindex}% + \bibopenbracket\usebibmacro{cite}\bibclosebracket} +{\supercitedelim} +{} +\let\cite=\supercite + +\usepackage{hyperref} +\usepackage[capitalise,noabbrev,nameinlink]{cleveref} +\crefdefaultlabelformat{#2\textbf{#1}#3} +\creflabelformat{equation}{#2\textbf{(#1)}#3} +\crefname{equation}{\textbf{Equation}}{\textbf{Equations}} +\Crefname{equation}{\textbf{Equation}}{\textbf{Equations}} +\crefname{figure}{\textbf{Figure}}{\textbf{Figures}} +\Crefname{figure}{\textbf{Figure}}{\textbf{Figures}} +\crefname{table}{\textbf{Table}}{\textbf{Tables}} +\Crefname{table}{\textbf{Table}}{\textbf{Tables}} +\crefname{appendix}{\textbf{Appendix}}{\textbf{Appendices}} +\Crefname{appendix}{\textbf{Appendix}}{\textbf{Appendices}} +\crefname{section}{\textbf{\S}}{\textbf{\S}} +\Crefname{section}{\textbf{\S}}{\textbf{\S}} +\crefname{algorithm}{\textbf{Algorithm}}{\textbf{Algorithms}} +\Crefname{algorithm}{\textbf{Algorithm}}{\textbf{Algorithms}} + +\hypersetup{ + unicode = true, + colorlinks = true, %Colours links instead of ugly boxes + urlcolor = blue, %Colour for external hyperlinks + linkcolor = magenta, %Colour of internal links + citecolor = cyan, %Colour of citations + linktocpage = true +} + +\DTMlangsetup*{ord=raise} + +\DeclareMathOperator{\sinc}{sinc} + +\setlength{\jot}{10pt} + +\setlist[itemize]{left=0pt} +\setlist[enumerate,1]{left=0pt} + +%\lstdefinestyle{code}{ +% backgroundcolor=\color{backcolour}, +% commentstyle=\color{codegreen}, +% keywordstyle=\color{magenta}, +% numberstyle=\tiny\color{codegray}, +% stringstyle=\color{codepurple}, +% basicstyle=\ttfamily\footnotesize, +% breakatwhitespace=false, +% breaklines=true, +% captionpos=b, +% keepspaces=true, +% numbers=left, +% numbersep=5pt, +% showspaces=false, +% showstringspaces=false, +% showtabs=false, +% tabsize=2 +%} + +%\lstset{style=code} diff --git a/docs/team/sharadhr/sharadhr.tex b/docs/team/sharadhr/sharadhr.tex new file mode 100644 index 00000000000..f16b76e85f6 --- /dev/null +++ b/docs/team/sharadhr/sharadhr.tex @@ -0,0 +1,87 @@ +\documentclass[11pt,british]{article} + +\include{preamble} + +\title{\vspace{-2.5cm} \textbf{CS2103T} \\ Team Project: CookBuddy} +\author{Sharadh Rajaraman \\ \textbf{A0189906L}} +\begin{document} +\maketitle +\section{Overview}\label{overview} +\href{https://github.com/AY1920S2-CS2103T-W12-4/main}{CookBuddy} is a desktop recipe manager for students living in on-campus accommodation, who enjoy cooking, and are also familiar with the command line. + +Users may interact with CookBuddy through a Command Line Interface (CLI), and it has a Graphical User Interface (GUI) created with JavaFX. + +CookBuddy is written in Java, and has about \num{10000} lines of code. + +\section{Summary of contributions}\label{summaryofcontributions} +\href{https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=W12&sort=groupTitle&sortWithin=title&since=2020-02-14&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=sharadhr&tabRepo=AY1920S2-CS2103T-W12-4\%2Fmain\%5Bmaster\%5D}{This RepoSense panel} displays this author's contributions to the project. + +\subsection{Enhancements Implemented} +\subsubsection{Major Enhancements} + +\paragraph{Added image reading, UI display, and processing (PR \href{https://github.com/AY1920S2-CS2103T-W12-4/main/pull/278}{\texttt{\#278}})}\label{imgcontrib} + +\begin{itemize} + \item \textbf{What it does:} Allows users to add images to their recipes, by specifying a relative or absolute + filepath. Images are saved in a data folder local to CookBuddy, and are named with a + UID (unique identifier). If an image cannot be found at the specified path, a placeholder is used instead. + + \item \textbf{Justification}: Users should be able to see what the final dish looks + like, and an image goes a very long way in showing that. Furthermore, images are a prominent feature of most competing recipe managers, and we feel this is a key feature that allows CookBuddy to compete on a similar standing. + + \item \textbf{Highlights}: + \begin{itemize} + \item Adding reading images required a firm understanding of file I/O, as well as understanding the various Java classes in \href{https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/nio/file/package-summary.html}{\texttt{java.nio.file}}. + + Appropriate methods for easy extensibility were implemented in \href{https://github.com/AY1920S2-CS2103T-W12-4/main/blob/master/src/main/java/cookbuddy/commons/util/PhotographUtil.java}{\texttt{cookbuddy.commons.util.PhotographUtil}}, as well as multiple constructors in \href{https://github.com/AY1920S2-CS2103T-W12-4/main/blob/master/src/main/java/cookbuddy/model/recipe/attribute/Photograph.java}{\texttt{cookbuddy.model.recipe.attribute.Photograph}}. + + \item Other file read/write methods were also implemented in \href{https://github.com/AY1920S2-CS2103T-W12-4/main/blob/master/src/main/java/cookbuddy/commons/util/FileUtil.java}{\texttt{cookbuddy.commons.util.FileUtil}}. + + \item \texttt{PhotographUtil} employs the \href{https://en.wikipedia.org/wiki/Singleton_pattern}{singleton pattern}, to prevent initialisation errors. + \end{itemize} +\end{itemize} + +\subsubsection{Minor Contributions} +\paragraph{Revamped the User Interface (UI) of CookBuddy (PR \href{https://github.com/AY1920S2-CS2103T-W12-4/main/pull/179}{\texttt{\#179}}) } +\begin{itemize} + \item A light theme was preferred by the team members + \item \href{https://pixelduke.com/java-javafx-theme-jmetro/}{\texttt{JMetro}} was used to change the theme + \item Appropriate classes and corresponding \texttt{.fxml} files were generated to produce the new UI. + \item The panels of the UI were also changed, with a fixed scrolling side panel for the recipe list view, and a selected recipe overview panel. +\end{itemize} + +\paragraph{Added Ingredient and Instruction List Attributes (PR \href{https://github.com/AY1920S2-CS2103T-W12-4/main/pull/125}{\texttt{\#125}}) } +\begin{itemize} + \item Ingredients and instructions were originally stored as un-delimited \texttt{String}s + \item Classes to represent \texttt{Ingredient}s, \texttt{Instruction}s and their containers were implemented +\end{itemize} + +\paragraph{Minor UI and Typeface Changes to Guides (Commit \href{https://github.com/AY1920S2-CS2103T-W12-4/main/pull/319/commits/01a9faffe83f12997f9eba13a2b92e6f4322167d}{\texttt{01a9faf}}) } + +\subsection{Documentation Contributions} +\subsubsection{User Guide} +\begin{itemize} + \item Added various feature details in \href{https://ay1920s2-cs2103t-w12-4.github.io/main/UserGuide.html#features}{\S{} 4} + \item Added data file information in \href{https://ay1920s2-cs2103t-w12-4.github.io/main/UserGuide.html#configuration-and-recipe-data-sharadh}{\S{} 5}. +\end{itemize} + +\subsubsection{Developer Guide} +\begin{itemize} + \item \href{https://ay1920s2-cs2103t-w12-4.github.io/main/DeveloperGuide.html#design}{\S{} 5}, \href{https://ay1920s2-cs2103t-w12-4.github.io/main/DeveloperGuide.html#image-management-done-by-sharadh-rajaraman}{\S{} 6.2} were added, detailing the overall architecture, and the implementation of image processing (see \cref{imgcontrib}). + \item Future work was briefly detailed in \href{https://ay1920s2-cs2103t-w12-4.github.io/main/DeveloperGuide.html#Implementation-Future}{\S{} 6.12}; + \item Appendices \href{https://ay1920s2-cs2103t-w12-4.github.io/main/DeveloperGuide.html#product-scope-done-by-zain-alam-sharadh-rajaraman}{A} and \href{https://ay1920s2-cs2103t-w12-4.github.io/main/DeveloperGuide.html#user-stories-done-by-sharadh-rajaraman-and-adarsh-chugani}{B} were added; in the latter, the User Stories were placed in a neat table. +\end{itemize} + +\subsection{Project Management and Teamwork} +\paragraph{Project Management} +\begin{itemize} + \item Set up the organisation, repository, and initial branches for all group members + \item Set up \texttt{Travis CI} and \texttt{AppVeyor} for initial continuous integration testing +\end{itemize} + +\paragraph{Teamwork} +\begin{itemize} + \item Assisted weaker members in setting up \texttt{git} and with iP tasks + \item Set up Zoom meetings to facilitate discussion of project proposals and code reviews, as well as implementation details +\end{itemize} +\end{document} diff --git a/docs/templates/_header.html.slim b/docs/templates/_header.html.slim index 3c2d5aed43c..ef277ee0065 100644 --- a/docs/templates/_header.html.slim +++ b/docs/templates/_header.html.slim @@ -1,25 +1,26 @@ / NOTE: You must restart the gradle daemon after modifying any template file for the changes to take effect. -- if !(attr? 'no-site-header') && (attr? 'site-seedu') - #seedu-header - nav.navbar.navbar-lg.navbar-light.bg-lighter - .container - a.navbar-brand href='https://se-edu.github.io/' - img src=(site_url 'images/SeEduLogo.png') alt='SE-EDU' - ul.navbar-nav - li.nav-item - a.nav-link href='https://se-edu.github.io/addressbook-level1' AB-1 - li.nav-item - a.nav-link href='https://se-edu.github.io/addressbook-level2' AB-2 - li.nav-item - a.nav-link.active href=(site_url 'index.html') AB-3 - li.nav-item - a.nav-link href='https://se-edu.github.io/addressbook-level4' AB-4 - li.nav-item - a.nav-link href='https://se-edu.github.io/collate' Collate - li.nav-item - a.nav-link href='https://se-edu.github.io/se-book' Book - li.nav-item - a.nav-link href='https://se-edu.github.io/learningresources' Resources +/- if !(attr? 'no-site-header') && (attr? 'site-seedu') +/ #seedu-header +/ nav.navbar.navbar-lg.navbar-light.bg-lighter +/ .container +/ a.navbar-brand href='https://se-edu.github.io/' +/ img src=(site_url 'images/SeEduLogo.png') alt='SE-EDU' +/ ul.navbar-nav +/ li.nav-item +/ a.nav-link href='https://se-edu.github.io/addressbook-level1' AB-1 +/ li.nav-item +/ a.nav-link href='https://se-edu.github.io/addressbook-level2' AB-2 +/ li.nav-item +/ a.nav-link.active href=(site_url 'index.html') AB-3 +/ li.nav-item +/ a.nav-link href='https://se-edu.github.io/addressbook-level4' AB-4 +/ li.nav-item +/ a.nav-link href='https://se-edu.github.io/collate' Collate +/ li.nav-item +/ a.nav-link href='https://se-edu.github.io/se-book' Book +/ li.nav-item +/ a.nav-link href='https://se-edu.github.io/learningresources' Resources +/ - if !(attr? 'no-site-header') #site-header diff --git a/docs/tutorials/AddRemark.adoc b/docs/tutorials/AddRemark.adoc index 51044c36494..218d128b67a 100644 --- a/docs/tutorials/AddRemark.adoc +++ b/docs/tutorials/AddRemark.adoc @@ -38,10 +38,10 @@ We accomplish that by returning a `CommandResult` with an accompanying message. ---- package seedu.address.logic.commands; -import seedu.address.model.Model; +import cookbuddy.Model; /** - * Changes the remark of an existing person in the address book. + * Changes the remark of an existing recipe in the address book. */ public class RemarkCommand extends Command { @@ -75,15 +75,15 @@ While we have successfully printed a message to `ResultDisplay`, the command doe Let's change the command to throw an `CommandException` to accurately reflect that our command is still a work in progress. .The relationship between RemarkCommand and Command -image::CommandInterface.png[] +image::CommandInterface.svg[] Following the convention in other commands, we add relevant messages as constants and use them. .RemarkCommand.java [source, java] ---- - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the remark of the person identified " - + "by the index number used in the last person listing. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the remark of the recipe identified " + + "by the index number used in the last recipe listing. " + "Existing remark will be overwritten by the input.\n" + "Parameters: INDEX (must be a positive integer) " + "r/ [REMARK]\n" @@ -120,8 +120,8 @@ public class RemarkCommand extends Command { private final String remark; /** - * @param index of the person in the filtered person list to edit the remark - * @param remark of the person to be updated to + * @param index of the recipe in the filtered recipe list to edit the remark + * @param remark of the recipe to be updated to */ public RemarkCommand(Index index, String remark) { requireAllNonNull(index, remark); @@ -164,7 +164,7 @@ Create a `RemarkCommandParser` class in the `seedu.address.logic.parser` package The class must extend the `Parser` interface. .The relationship between Parser and RemarkCommandParser -image::ParserInterface.png[] +image::ParserInterface.svg[] Thankfully, `ArgumentTokenizer#tokenize()` makes it trivial to parse user input. Let's take a look at the JavaDoc provided for the function to understand what it does. @@ -246,12 +246,12 @@ If you are stuck, check out the sample link:https://github.com/nus-cs2103-AY1920 Now that we have all the information that we need, let's lay the groundwork for some _persistent_ changes. We achieve that by working with the `Person` model. -Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the person's name). -That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a person. +Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the recipe's name). +That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a recipe. === Add a new `Remark` class -Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. +Create a new `Remark` in `recipe`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. A copy-paste and search-replace later, you should have something like link:https://github.com/nus-cs2103-AY1920S1/addressbook-level3/commit/b7a47c50c8e5f0430d343a23d2863446b6ce9298#diff-af2f075d24dfcd333876f0fbce321f25[this]. Note how `Remark` has no constrains and thus does not require input validation. @@ -263,7 +263,7 @@ These should be relatively simple changes. == Add a placeholder element for remark to the UI -Without getting too deep into `fxml`, let's go on a 5 minute adventure to get some placeholder text to show up for each person. +Without getting too deep into `fxml`, let's go on a 5 minute adventure to get some placeholder text to show up for each recipe. Simply add [source, java] @@ -326,9 +326,9 @@ Just add link:https://github.com/nus-cs2103-AY1920S1/addressbook-level3/commit/5 [source, java] .PersonCard.java ---- -public PersonCard(Person person, int displayedIndex) { +public PersonCard(Person recipe, int displayedIndex) { //... - remark.setText(person.getRemark().value); + remark.setText(recipe.getRemark().value); } ---- diff --git a/docs/tutorials/RemovingFields.adoc b/docs/tutorials/RemovingFields.adoc index 5a50b6965a6..0e0c3db407b 100644 --- a/docs/tutorials/RemovingFields.adoc +++ b/docs/tutorials/RemovingFields.adoc @@ -24,7 +24,7 @@ Fortunately, the IntelliJ IDEA provides a robust refactoring tool that can ident Let's try to use it as much as we can. === Assisted refactoring -The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. +The `address` field in `Person` is actually an instance of the `seedu.address.model.recipe.Address` class. Since removing the `Address` class will break the application, we start by identifying ``Address``'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` > `Safe Delete` through the menu. diff --git a/docs/tutorials/TracingCode.adoc b/docs/tutorials/TracingCode.adoc index 5f0aaba1741..41e10cd6158 100644 --- a/docs/tutorials/TracingCode.adoc +++ b/docs/tutorials/TracingCode.adoc @@ -21,12 +21,12 @@ Before we jump into the code, it is useful to get an idea of the overall structu application. This is provided in the 'Architecture' section of the developer guide. In particular, the architecture diagram (reproduced below), tells us that the App consists of several components. -image::../ArchitectureDiagram.png[] +image::../ArchitectureDiagram.svg[] It also has a sequence diagram (reproduced below) that tells us how a command propagates through the App. .Architecture sequence diagram from the developer guide -image::../ArchitectureSequenceDiagram.png[] +image::../ArchitectureSequenceDiagram.svg[] Note how the diagram shows only how the execution flows _between_ the main components. That is, it does not show details of the execution path _inside_ each component. By hiding those details, the diagram succeeds in informing the reader @@ -49,7 +49,7 @@ However, the execution path through a GUI is often somewhat obscure due to vario used by GUI frameworks, which happens to be the case here too. Therefore, let us put the breakpoint where the UI transfers control to the Logic component. According to the sequence diagram, the UI component yields control to the Logic component through a method named `execute`. Searching through the code base for `execute()` yields a promising candidate in -`seedu.address.ui.CommandBox.CommandExecutor`. +`cookbuddy.ui.CommandBox.CommandExecutor`. .Using the `Search for target by name` feature. `Navigate` > `Symbol`. image::Execute.png[] @@ -169,7 +169,7 @@ The sequence diagram below shows the details of the execution path through the L Does the execution path you traced in the code so far matches with the diagram? + .Tracing an `edit` command through the Logic component -image::LogicSequenceDiagram.png[] +image::LogicSequenceDiagram.svg[] . Now let's see what happens when we call `command#execute()`! + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 87b738cbd05..f3d88b1c2fa 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 44e7c4d1d7b..4a6ebceacd2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index af6708ff229..2fe81a7d95e 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m"' +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -109,8 +125,8 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` @@ -138,19 +154,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +175,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 6d57edc706c..62bd9b9ccef 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,8 +29,11 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/cookbuddy/AppParameters.java similarity index 86% rename from src/main/java/seedu/address/AppParameters.java rename to src/main/java/cookbuddy/AppParameters.java index ab552c398f3..ab5b63bfd22 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/cookbuddy/AppParameters.java @@ -1,4 +1,4 @@ -package seedu.address; +package cookbuddy; import java.nio.file.Path; import java.nio.file.Paths; @@ -6,9 +6,9 @@ import java.util.Objects; import java.util.logging.Logger; +import cookbuddy.commons.core.LogsCenter; +import cookbuddy.commons.util.FileUtil; import javafx.application.Application; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.FileUtil; /** * Represents the parsed command-line parameters given to the application. @@ -34,11 +34,13 @@ public static AppParameters parse(Application.Parameters parameters) { Map namedParameters = parameters.getNamed(); String configPathParameter = namedParameters.get("config"); - if (configPathParameter != null && !FileUtil.isValidPath(configPathParameter)) { + if (configPathParameter != null && !FileUtil.isValidPathStrings(configPathParameter)) { logger.warning("Invalid config path " + configPathParameter + ". Using default config path."); configPathParameter = null; } - appParameters.setConfigPath(configPathParameter != null ? Paths.get(configPathParameter) : null); + appParameters.setConfigPath(configPathParameter != null + ? Paths.get(configPathParameter) + : null); return appParameters; } diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/cookbuddy/Main.java similarity index 84% rename from src/main/java/seedu/address/Main.java rename to src/main/java/cookbuddy/Main.java index 052a5068631..82ab58104b2 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/cookbuddy/Main.java @@ -1,20 +1,20 @@ -package seedu.address; +package cookbuddy; import javafx.application.Application; /** * The main entry point to the application. - * + *

* This is a workaround for the following error when MainApp is made the * entry point of the application: - * - * Error: JavaFX runtime components are missing, and are required to run this application - * + *

+ * Error: JavaFX runtime components are missing, and are required to run this application + *

* The reason is that MainApp extends Application. In that case, the * LauncherHelper will check for the javafx.graphics module to be present * as a named module. We don't use JavaFX via the module system so it can't * find the javafx.graphics module, and so the launch is aborted. - * + *

* By having a separate main class (Main) that doesn't extend Application * to be the entry point of the application, we avoid this issue. */ diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/cookbuddy/MainApp.java similarity index 62% rename from src/main/java/seedu/address/MainApp.java rename to src/main/java/cookbuddy/MainApp.java index e5cfb161b73..20dc82b3c53 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/cookbuddy/MainApp.java @@ -1,35 +1,35 @@ -package seedu.address; +package cookbuddy; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; import java.util.logging.Logger; +import cookbuddy.commons.core.Config; +import cookbuddy.commons.core.LogsCenter; +import cookbuddy.commons.core.Version; +import cookbuddy.commons.exceptions.DataConversionException; +import cookbuddy.commons.util.ConfigUtil; +import cookbuddy.commons.util.StringUtil; +import cookbuddy.logic.Logic; +import cookbuddy.logic.LogicManager; +import cookbuddy.model.Model; +import cookbuddy.model.ModelManager; +import cookbuddy.model.ReadOnlyRecipeBook; +import cookbuddy.model.ReadOnlyUserPrefs; +import cookbuddy.model.RecipeBook; +import cookbuddy.model.UserPrefs; +import cookbuddy.model.util.SampleDataUtil; +import cookbuddy.storage.JsonRecipeBookStorage; +import cookbuddy.storage.JsonUserPrefsStorage; +import cookbuddy.storage.RecipeBookStorage; +import cookbuddy.storage.Storage; +import cookbuddy.storage.StorageManager; +import cookbuddy.storage.UserPrefsStorage; +import cookbuddy.ui.Ui; +import cookbuddy.ui.UiManager; import javafx.application.Application; import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.core.Version; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.ConfigUtil; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; -import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -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; -import seedu.address.storage.Storage; -import seedu.address.storage.StorageManager; -import seedu.address.storage.UserPrefsStorage; -import seedu.address.ui.Ui; -import seedu.address.ui.UiManager; /** * Runs the application. @@ -48,7 +48,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing CookBuddy ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -56,8 +56,9 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); UserPrefs userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + RecipeBookStorage recipeBookStorage = new JsonRecipeBookStorage(userPrefs.getDataFilePath(), + userPrefs.getImagesPath()); + storage = new StorageManager(recipeBookStorage, userPrefsStorage); initLogging(config); @@ -69,25 +70,28 @@ public void init() throws Exception { } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found, - * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. + * Returns a {@code ModelManager} with the data from {@code storage}'s recipe + * book and {@code userPrefs}.
+ * The data from the sample recipe book will be used instead if + * {@code storage}'s recipe book is not found, or an empty recipe book will be + * used instead if errors occur when reading {@code storage}'s recipe book. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional recipeBookOptional; + ReadOnlyRecipeBook initialData; try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + recipeBookOptional = storage.readRecipeBook(); + if (!recipeBookOptional.isPresent()) { + logger.info("Data file not found. Populating RecipeBook with sample recipes."); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = new RecipeBook(); + initialData = recipeBookOptional.orElseGet(SampleDataUtil::getSampleRecipeBook); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Data file not in the correct format. Will be starting with an empty RecipeBook"); + initialData = new RecipeBook(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Problem while reading from the file. Will be starting with an empty RecipeBook"); + initialData = new RecipeBook(); } return new ModelManager(initialData, userPrefs); @@ -124,7 +128,8 @@ protected Config initConfig(Path configFilePath) { initializedConfig = new Config(); } - //Update config file in case it was missing to begin with or there are new/unused fields + // Update config file in case it was missing to begin with or there are + // new/unused fields try { ConfigUtil.saveConfig(initializedConfig, configFilePathUsed); } catch (IOException e) { @@ -134,9 +139,9 @@ protected Config initConfig(Path configFilePath) { } /** - * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs file path, - * or a new {@code UserPrefs} with default configuration if errors occur when - * reading from the file. + * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs + * file path, or a new {@code UserPrefs} with default configuration if errors + * occur when reading from the file. */ protected UserPrefs initPrefs(UserPrefsStorage storage) { Path prefsFilePath = storage.getUserPrefsFilePath(); @@ -151,11 +156,12 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty RecipeBook"); initializedPrefs = new UserPrefs(); } - //Update prefs file in case it was missing to begin with or there are new/unused fields + // Update prefs file in case it was missing to begin with or there are + // new/unused fields try { storage.saveUserPrefs(initializedPrefs); } catch (IOException e) { @@ -167,13 +173,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting CookBuddy " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping CookBuddy ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/cookbuddy/commons/core/Config.java similarity index 97% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/cookbuddy/commons/core/Config.java index 91145745521..5fb017c2d27 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/cookbuddy/commons/core/Config.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package cookbuddy.commons.core; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/cookbuddy/commons/core/GuiSettings.java similarity index 97% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/cookbuddy/commons/core/GuiSettings.java index 5ace559ad15..544b2902f3c 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/cookbuddy/commons/core/GuiSettings.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package cookbuddy.commons.core; import java.awt.Point; import java.io.Serializable; @@ -8,6 +8,7 @@ * A Serializable class that contains the GUI settings. * Guarantees: immutable. */ +@SuppressWarnings("serial") public class GuiSettings implements Serializable { private static final double DEFAULT_HEIGHT = 600; diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/cookbuddy/commons/core/LogsCenter.java similarity index 99% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/cookbuddy/commons/core/LogsCenter.java index 431e7185e76..6db633f9bae 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/cookbuddy/commons/core/LogsCenter.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package cookbuddy.commons.core; import java.io.IOException; import java.util.Arrays; diff --git a/src/main/java/cookbuddy/commons/core/Messages.java b/src/main/java/cookbuddy/commons/core/Messages.java new file mode 100644 index 00000000000..8416bb5cc6c --- /dev/null +++ b/src/main/java/cookbuddy/commons/core/Messages.java @@ -0,0 +1,14 @@ +package cookbuddy.commons.core; + +/** + * Container for user visible messages. + */ +public class Messages { + + public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; + public static final String MESSAGE_INVALID_RECIPE_DISPLAYED_INDEX = "The recipe index provided is invalid"; + public static final String MESSAGE_EMPTY_RECIPE_BOOK = "There are no recipes in the recipe book!"; + public static final String MESSAGE_RECIPES_LISTED_OVERVIEW = "%1$d recipes found!"; + +} diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/cookbuddy/commons/core/Version.java similarity index 98% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/cookbuddy/commons/core/Version.java index e117f91b3b2..5b3b98f1cac 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/cookbuddy/commons/core/Version.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package cookbuddy.commons.core; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/cookbuddy/commons/core/index/Index.java similarity index 97% rename from src/main/java/seedu/address/commons/core/index/Index.java rename to src/main/java/cookbuddy/commons/core/index/Index.java index 19536439c09..ac856657955 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/cookbuddy/commons/core/index/Index.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core.index; +package cookbuddy.commons.core.index; /** * Represents a zero-based or one-based index. diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/cookbuddy/commons/exceptions/DataConversionException.java similarity index 77% rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java rename to src/main/java/cookbuddy/commons/exceptions/DataConversionException.java index 1f689bd8e3f..9f4ef3e2760 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java +++ b/src/main/java/cookbuddy/commons/exceptions/DataConversionException.java @@ -1,8 +1,9 @@ -package seedu.address.commons.exceptions; +package cookbuddy.commons.exceptions; /** * Represents an error during conversion of data from one format to another */ +@SuppressWarnings("serial") public class DataConversionException extends Exception { public DataConversionException(Exception cause) { super(cause); diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/cookbuddy/commons/exceptions/IllegalValueException.java similarity index 89% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/cookbuddy/commons/exceptions/IllegalValueException.java index 19124db485c..48e752390e8 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/cookbuddy/commons/exceptions/IllegalValueException.java @@ -1,8 +1,9 @@ -package seedu.address.commons.exceptions; +package cookbuddy.commons.exceptions; /** * Signals that some given data does not fulfill some constraints. */ +@SuppressWarnings("serial") public class IllegalValueException extends Exception { /** * @param message should contain relevant information on the failed constraint(s) diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/cookbuddy/commons/util/AppUtil.java similarity index 72% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/cookbuddy/commons/util/AppUtil.java index da90201dfd6..6e50df61f4a 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/cookbuddy/commons/util/AppUtil.java @@ -1,19 +1,10 @@ -package seedu.address.commons.util; - -import static java.util.Objects.requireNonNull; - -import javafx.scene.image.Image; -import seedu.address.MainApp; +package cookbuddy.commons.util; /** * A container for App specific utility functions */ public class AppUtil { - public static Image getImage(String imagePath) { - requireNonNull(imagePath); - return new Image(MainApp.class.getResourceAsStream(imagePath)); - } /** * Checks that {@code condition} is true. Used for validating arguments to methods. diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/cookbuddy/commons/util/CollectionUtil.java similarity index 96% rename from src/main/java/seedu/address/commons/util/CollectionUtil.java rename to src/main/java/cookbuddy/commons/util/CollectionUtil.java index eafe4dfd681..eeb712bb03d 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/cookbuddy/commons/util/CollectionUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package cookbuddy.commons.util; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/cookbuddy/commons/util/ConfigUtil.java similarity index 77% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/cookbuddy/commons/util/ConfigUtil.java index f7f8a2bd44c..149f1088c0e 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/cookbuddy/commons/util/ConfigUtil.java @@ -1,11 +1,11 @@ -package seedu.address.commons.util; +package cookbuddy.commons.util; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.core.Config; -import seedu.address.commons.exceptions.DataConversionException; +import cookbuddy.commons.core.Config; +import cookbuddy.commons.exceptions.DataConversionException; /** * A class for accessing the Config File. diff --git a/src/main/java/cookbuddy/commons/util/FileUtil.java b/src/main/java/cookbuddy/commons/util/FileUtil.java new file mode 100644 index 00000000000..c78afe33bf3 --- /dev/null +++ b/src/main/java/cookbuddy/commons/util/FileUtil.java @@ -0,0 +1,182 @@ +package cookbuddy.commons.util; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import cookbuddy.Main; + +/** + * Writes and reads files + */ +public class FileUtil { + + public static final String CORRUPTED = "Your JAR file is corrupt, and possibly missing files. " + + "Please re-download from the source."; + private static final String CHARSET = "UTF-8"; + + public static boolean isFileExists(Path file) { + return Files.exists(file) && Files.isRegularFile(file); + } + + /** + * Returns {@code true} if {@code pathString} can be represented as a valid + * {@link Path}. + * + * @param pathString A {@link String} of a filesystem path. + * @return duh. + */ + public static boolean isValidPathString(String pathString) { + try { + relativePathFrom(pathString); + } catch (InvalidPathException e) { + return false; + } + return true; + } + + /** + * Returns true if {@code pathStrings} can be converted into a {@code Path} via + * {@link Paths#get(String)}, otherwise returns false. + * + * @param pathStrings A comma-separated list of {@link String}s representing the + * file path. Cannot be null. + */ + public static boolean isValidPathStrings(String... pathStrings) { + try { + relativePathFrom(pathStrings); + } catch (InvalidPathException ipe) { + return false; + } + return true; + } + + /** + * Returns a relative {@link Path} from {@code pathStrings} by using + * the OS's file system separator ({@code /} on *nix, {@code \} on Windows). + *

+ * For example, {@code "path", "foo", "bar"} is returned as + * {@code path/foo/bar}. + * + * @param pathStrings A comma-separated list of {@link Strings} which are + * concatenated + * @return An absolute file path. + */ + public static Path relativePathFrom(String... pathStrings) { + return Paths.get("", pathStrings).normalize(); + } + + /** + * Returns an absolute {@link Path} from {@code pathStrings} by using + * the OS's file system separator ({@code /} on *nix, {@code \} on Windows). + *

+ * For example, {@code "path", "foo", "bar"} is returned as + * {@code path/foo/bar}. + * + * @param pathStrings A comma-separated list of {@link Strings} which are + * concatenated + * @return An absolute file path. + */ + public static Path absolutePathFrom(String... pathStrings) { + return Paths.get("", pathStrings).toAbsolutePath().normalize(); + } + + /** + * Appends {@code paths} to {@code p1}, in the order of appearance. + * + * @param p1 The highest path + * @param paths A comma-separated list of {@link Path}s to append to {@code p1}. + * @return An appended {@link Path}. + */ + public static Path joinPaths(Path p1, Path... paths) { + Path returnable = p1; + for (Path path : paths) { + returnable = returnable.resolve(path); + } + return returnable; + } + + /** + * Creates a file if it does not exist along with its missing parent + * directories. + * + * @throws IOException if the file or directory cannot be created. + */ + public static void createIfMissing(Path file) throws IOException { + if (!isFileExists(file)) { + createFile(file); + } + } + + /** + * Creates a file if it does not exist along with its missing parent + * directories. + */ + public static void createFile(Path file) throws IOException { + if (Files.exists(file)) { + return; + } + + createParentDirsOfFile(file); + Files.createFile(file); + } + + /** + * Creates parent directories of file if it has a parent directory + */ + public static void createParentDirsOfFile(Path file) throws IOException { + Path parentDir = file.getParent(); + + if (parentDir != null) { + Files.createDirectories(parentDir); + } + } + + /** + * Assumes file exists + */ + public static String readFromFile(Path file) throws IOException { + return new String(Files.readAllBytes(file), CHARSET); + } + + /** + * Writes given string to a file. Will create the file if it does not exist yet. + */ + public static void writeToFile(Path file, String content) throws IOException { + Files.write(file, content.getBytes(CHARSET)); + } + + /** + * Returns an {@link InputStream} from {@code file}; the stream is buffered. + * + * @param file A {@link Path} to the file in question + * @return + * @throws FileNotFoundException if the file is not found. + */ + public static InputStream streamFromFile(File file) throws FileNotFoundException { + return new BufferedInputStream(new FileInputStream(file)); + } + + /** + * Returns an {@link InputStream} from the file located at {@code filePath}. + * + * @param filePath A {@link Path} to a file in the filesystem. + * @return An input stream of {@code filePath}. + * @throws IOException As specified by + * {@link Files#newInputStream(Path, java.nio.file.OpenOption...)}. + */ + public static InputStream streamFromPath(Path filePath) throws IOException { + return new BufferedInputStream(Files.newInputStream(filePath)); + } + + public static InputStream getResourceAsInputStream(String resourceString) { + return new BufferedInputStream(Main.class.getResourceAsStream(resourceString)); + } +} diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/cookbuddy/commons/util/JsonUtil.java similarity index 97% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/cookbuddy/commons/util/JsonUtil.java index 8ef609f055d..d72b195e125 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/cookbuddy/commons/util/JsonUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package cookbuddy.commons.util; import static java.util.Objects.requireNonNull; @@ -20,8 +20,8 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; +import cookbuddy.commons.core.LogsCenter; +import cookbuddy.commons.exceptions.DataConversionException; /** * Converts a Java object instance to JSON and vice versa @@ -113,6 +113,7 @@ public static String toJsonString(T instance) throws JsonProcessingException /** * Contains methods that retrieve logging level from serialized string. */ + @SuppressWarnings("serial") private static class LevelDeserializer extends FromStringDeserializer { protected LevelDeserializer(Class vc) { diff --git a/src/main/java/cookbuddy/commons/util/PhotographUtil.java b/src/main/java/cookbuddy/commons/util/PhotographUtil.java new file mode 100644 index 00000000000..d90aad9993c --- /dev/null +++ b/src/main/java/cookbuddy/commons/util/PhotographUtil.java @@ -0,0 +1,193 @@ +package cookbuddy.commons.util; + +import static cookbuddy.commons.util.FileUtil.getResourceAsInputStream; + +import java.awt.image.BufferedImage; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.logging.Logger; + +import javax.imageio.ImageIO; + +import cookbuddy.MainApp; +import cookbuddy.commons.core.LogsCenter; +import cookbuddy.model.recipe.Recipe; +import javafx.collections.ObservableList; + +/** + * A singleton class for working with images, converting between image objects, + * reading and writing from image files, and to simplify and reduce calls to + * {@link ImageIO} repeatedly. + *

+ * This class follows the singleton pattern + * ({@link https://www.geeksforgeeks.org/singleton-class-java/}), because + * deadlocks were encountered when initialising static variables, when this was + * originally a utility class. This pattern would originally fail several + * CheckStyle checks, which have since been disabled. + */ +public class PhotographUtil { + public static final String PLACEHOLDER_IMAGE_PATH_STRING = "/images/recipe_placeholder.jpg"; + public static final Path PLACEHOLDER_IMAGE_PATH = Paths.get(PLACEHOLDER_IMAGE_PATH_STRING); + public final InputStream placeHolderImageStream; + public final BufferedImage placeholderImage; + public final Path defaultStoragePath = FileUtil.relativePathFrom("data", "images"); + public final String messageConstraints = "Image not found, or invalid image path given."; + private final Logger logger = LogsCenter.getLogger(MainApp.class); + + private PhotographUtil() { + this.placeHolderImageStream = getResourceAsInputStream(PLACEHOLDER_IMAGE_PATH_STRING); + this.placeholderImage = getImage(placeHolderImageStream); + } + + public static PhotographUtil imageUtil() { + return new PhotographUtil(); + } + + /** + * Returns a {@link BufferedImage} from the given {@code Path} + * + * @param path An image file path + * @return An image object + */ + public BufferedImage readImage(Path path) { + try { + return ImageIO.read(path.toFile()); + } catch (IOException e) { + logger.warning(messageConstraints); + return this.placeholderImage; + } + } + + /** + * Returns an {@link InputStream} from the given {@code image}, by invoking a + * {@link ByteArrayOutputStream} and copying the data out. + * + * @param image An image object + * @return A data object representing that image + */ + public InputStream getImageInputStream(BufferedImage image) { + ByteArrayOutputStream baos = new ByteArrayOutputStream() { + @Override + public synchronized byte[] toByteArray() { + return this.buf; + } + }; + + try { + ImageIO.write(image, "png", baos); + } catch (IOException e) { + logger.warning("An error has occurred. Using placeholder image instead."); + return placeHolderImageStream; + } + return new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())); + } + + /** + * Returns a {@link BufferedImage} from the given {@code imageInputStream} + * + * @param imageInputStream A data object representing an image. + * @return An image object. + */ + public BufferedImage getImage(InputStream imageInputStream) { + try { + return ImageIO.read(imageInputStream); + } catch (IOException e) { + logger.warning("An error has occurred. Using placeholder image instead."); + return placeholderImage; + } + } + + /** + * Returns a {@link BufferedImage} from the specified {@code url}. + * + * @param url The {@link URL} that the image is located at. + * @return The image at the URL. + */ + public BufferedImage getImage(URL url) { + try { + return ImageIO.read(url); + } catch (IOException e) { + logger.warning("An error has occurred. Using placeholder image instead."); + return placeholderImage; + } + } + + /** + * Compares two {@link BufferedImage}s, specified by {@code i1} and {@code i2}, + * pixel-by-pixel, to determine if they are the same image. + *

+ * Expensive O(n^2) operation; use sparingly. + * + * @return {@code true} if {@code i1}'s pixels are all equal to those of + * {@code i2}'s. + */ + public boolean isSameImage(BufferedImage i1, BufferedImage i2) { + if (i1.getWidth() != i2.getWidth() || i1.getHeight() != i2.getHeight()) { + return false; + } + + for (int x = 0; x < i1.getWidth(); x++) { + for (int y = 0; y < i2.getHeight(); y++) { + if (i1.getRGB(x, y) != i2.getRGB(x, y)) { + return false; + } + } + } + return true; + } + + /** + * See {@link #isPlaceHolderImage(Path)}. + */ + public static boolean isPlaceHolderImage(String... imageFilePathStrings) { + return isPlaceHolderImage(FileUtil.relativePathFrom(imageFilePathStrings)); + } + + /** + * Checks if {@link imageFilePath} refers to the placeholder image. + * + * @param imageFilePath A {@link Path} where an image is stored. + * @return {@code true} if {@link imageFilePath} resolves to the placeholder. + */ + public static boolean isPlaceHolderImage(Path imageFilePath) { + return imageFilePath.compareTo(PLACEHOLDER_IMAGE_PATH) == 0 + || imageFilePath.compareTo(FileUtil.relativePathFrom("data", "images", "placeholder")) == 0; + } + + /** + * Writes all images in {@code recipeList} to disk; skips if the file already + * exists. + * + * @param recipeList An unmodifiable list of recipes whose images are to be + * saved. + * @param imageFilePath + * @throws IOException + */ + public void saveAllImages(ObservableList recipeList, Path imageFilePath) throws IOException { + for (Recipe recipe : recipeList) { + Path recipeImagePath = FileUtil.joinPaths(imageFilePath, recipe.getPhotograph().getImageFileName(recipe)); + if (!isPlaceHolderImage(recipeImagePath) && !FileUtil.isFileExists(recipeImagePath)) { + FileUtil.createIfMissing(recipeImagePath); + ImageIO.write(recipe.getPhotograph().getData(), "png", recipeImagePath.toFile()); + } + } + } + + /** + * Generates a hashcode for {@code image}, based on its RGB pixel data. Two + * identical images should return the same hashcode. + * + * @param image A {@link BufferedImage} to hash + * @return The hashcode. + */ + public int hashImage(BufferedImage image) { + return Arrays.hashCode(image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth())); + } +} diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/cookbuddy/commons/util/StringUtil.java similarity index 95% rename from src/main/java/seedu/address/commons/util/StringUtil.java rename to src/main/java/cookbuddy/commons/util/StringUtil.java index 61cc8c9a1cb..5fa648f9af6 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/cookbuddy/commons/util/StringUtil.java @@ -1,7 +1,7 @@ -package seedu.address.commons.util; +package cookbuddy.commons.util; +import static cookbuddy.commons.util.AppUtil.checkArgument; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; import java.io.PrintWriter; import java.io.StringWriter; diff --git a/src/main/java/cookbuddy/logic/CommandHistory.java b/src/main/java/cookbuddy/logic/CommandHistory.java new file mode 100644 index 00000000000..ee53327a8cd --- /dev/null +++ b/src/main/java/cookbuddy/logic/CommandHistory.java @@ -0,0 +1,81 @@ +package cookbuddy.logic; + +import java.util.LinkedList; +import java.util.ListIterator; + +/** + * Helper class that is responsible for storing commands entered by user. + */ +public class CommandHistory { + private final LinkedList commandHistory = new LinkedList(); + private ListIterator iterator; + private int maxSize; + + /** + * Uses the default {@code maxSize} of 50 for history. + */ + public CommandHistory() { + maxSize = 50; + } + + /** + * Adds {@code cmd} to command history. + * @param cmd The command to be stored in the history. + */ + public void addCommand(String cmd) { + commandHistory.addFirst(cmd); + if (commandHistory.size() > maxSize) { + commandHistory.removeLast(); + } + this.iterator = null; + } + + /** + * Returns the command history size. + * @return the size of the command history. + */ + public int size() { + return commandHistory.size(); + } + + /** + * Resets the iterator. + */ + public void resetIterator() { + iterator = null; + } + + /** + * Returns the previous command. + * @return the previous command. + */ + public String iterateNext() { + if (this.iterator == null) { + this.iterator = commandHistory.listIterator(0); + } + if (this.iterator.hasNext()) { + return this.iterator.next(); + } + return null; + } + + /** + * Returns the next command. + * @return the next command. + */ + public String iteratePrevious() { + if (this.iterator != null) { + if (this.iterator.hasPrevious()) { + this.iterator.previous(); + } + if (this.iterator.hasPrevious()) { + String result = this.iterator.previous(); + this.iterator.next(); + return result; + } else { + return ""; + } + } + return null; + } +} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/cookbuddy/logic/Logic.java similarity index 53% rename from src/main/java/seedu/address/logic/Logic.java rename to src/main/java/cookbuddy/logic/Logic.java index 92cd8fa605a..016544af280 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/cookbuddy/logic/Logic.java @@ -1,14 +1,15 @@ -package seedu.address.logic; +package cookbuddy.logic; import java.nio.file.Path; +import cookbuddy.commons.core.GuiSettings; +import cookbuddy.logic.commands.CommandResult; +import cookbuddy.logic.commands.exceptions.CommandException; +import cookbuddy.logic.parser.exceptions.ParseException; +import cookbuddy.model.Model; +import cookbuddy.model.ReadOnlyRecipeBook; +import cookbuddy.model.recipe.Recipe; import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; /** * API of the Logic component @@ -24,19 +25,19 @@ public interface Logic { CommandResult execute(String commandText) throws CommandException, ParseException; /** - * Returns the AddressBook. + * Returns the RecipeBook. * - * @see seedu.address.model.Model#getAddressBook() + * @see Model#getRecipeBook() */ - ReadOnlyAddressBook getAddressBook(); + ReadOnlyRecipeBook getRecipeBook(); - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of recipes */ + ObservableList getFilteredRecipeList(); /** - * Returns the user prefs' address book file path. + * Returns the user prefs' recipe book file path. */ - Path getAddressBookFilePath(); + Path getRecipeBookFilePath(); /** * Returns the user prefs' GUI settings. diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/cookbuddy/logic/LogicManager.java similarity index 54% rename from src/main/java/seedu/address/logic/LogicManager.java rename to src/main/java/cookbuddy/logic/LogicManager.java index d47ce874b1a..57085207d96 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/cookbuddy/logic/LogicManager.java @@ -1,21 +1,21 @@ -package seedu.address.logic; +package cookbuddy.logic; import java.io.IOException; import java.nio.file.Path; import java.util.logging.Logger; +import cookbuddy.commons.core.GuiSettings; +import cookbuddy.commons.core.LogsCenter; +import cookbuddy.logic.commands.Command; +import cookbuddy.logic.commands.CommandResult; +import cookbuddy.logic.commands.exceptions.CommandException; +import cookbuddy.logic.parser.RecipeBookParser; +import cookbuddy.logic.parser.exceptions.ParseException; +import cookbuddy.model.Model; +import cookbuddy.model.ReadOnlyRecipeBook; +import cookbuddy.model.recipe.Recipe; +import cookbuddy.storage.Storage; import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; -import seedu.address.storage.Storage; /** * The main LogicManager of the app. @@ -26,12 +26,12 @@ public class LogicManager implements Logic { private final Model model; private final Storage storage; - private final AddressBookParser addressBookParser; + private final RecipeBookParser recipeBookParser; public LogicManager(Model model, Storage storage) { this.model = model; this.storage = storage; - addressBookParser = new AddressBookParser(); + recipeBookParser = new RecipeBookParser(); } @Override @@ -39,11 +39,11 @@ public CommandResult execute(String commandText) throws CommandException, ParseE logger.info("----------------[USER COMMAND][" + commandText + "]"); CommandResult commandResult; - Command command = addressBookParser.parseCommand(commandText); + Command command = recipeBookParser.parseCommand(commandText); commandResult = command.execute(model); try { - storage.saveAddressBook(model.getAddressBook()); + storage.saveRecipeBook(model.getRecipeBook()); } catch (IOException ioe) { throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); } @@ -52,18 +52,18 @@ public CommandResult execute(String commandText) throws CommandException, ParseE } @Override - public ReadOnlyAddressBook getAddressBook() { - return model.getAddressBook(); + public ReadOnlyRecipeBook getRecipeBook() { + return model.getRecipeBook(); } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredRecipeList() { + return model.getFilteredRecipeList(); } @Override - public Path getAddressBookFilePath() { - return model.getAddressBookFilePath(); + public Path getRecipeBookFilePath() { + return model.getRecipeBookFilePath(); } @Override diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/cookbuddy/logic/commands/Command.java similarity index 78% rename from src/main/java/seedu/address/logic/commands/Command.java rename to src/main/java/cookbuddy/logic/commands/Command.java index 64f18992160..39890b1cd43 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/cookbuddy/logic/commands/Command.java @@ -1,7 +1,7 @@ -package seedu.address.logic.commands; +package cookbuddy.logic.commands; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import cookbuddy.logic.commands.exceptions.CommandException; +import cookbuddy.model.Model; /** * Represents a command with hidden internal logic and the ability to be executed. diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/cookbuddy/logic/commands/CommandResult.java similarity index 97% rename from src/main/java/seedu/address/logic/commands/CommandResult.java rename to src/main/java/cookbuddy/logic/commands/CommandResult.java index 92f900b7916..c524091a417 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/cookbuddy/logic/commands/CommandResult.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package cookbuddy.logic.commands; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/cookbuddy/logic/commands/CountCommand.java b/src/main/java/cookbuddy/logic/commands/CountCommand.java new file mode 100644 index 00000000000..1cfc105f7cc --- /dev/null +++ b/src/main/java/cookbuddy/logic/commands/CountCommand.java @@ -0,0 +1,27 @@ +package cookbuddy.logic.commands; + +import static java.util.Objects.requireNonNull; + +import cookbuddy.model.Model; + +/** + * Counts all recipes in the recipe book and displays the total number to the user. + */ +public class CountCommand extends Command { + + public static final String COMMAND_WORD = "count"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Displays the total number of recipes in CookBuddy.\n" + + "Example: " + COMMAND_WORD; + + public static final String MESSAGE_SUCCESS = "Total available recipes are: "; + + private static long total; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + total = model.count(); + return new CommandResult(MESSAGE_SUCCESS + total); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/cookbuddy/logic/commands/DeleteCommand.java similarity index 56% rename from src/main/java/seedu/address/logic/commands/DeleteCommand.java rename to src/main/java/cookbuddy/logic/commands/DeleteCommand.java index 02fd256acba..90acef4848b 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/cookbuddy/logic/commands/DeleteCommand.java @@ -1,28 +1,28 @@ -package seedu.address.logic.commands; +package cookbuddy.logic.commands; import static java.util.Objects.requireNonNull; import java.util.List; -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; +import cookbuddy.commons.core.Messages; +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.exceptions.CommandException; +import cookbuddy.model.Model; +import cookbuddy.model.recipe.Recipe; /** - * Deletes a person identified using it's displayed index from the address book. + * Deletes a recipe identified using it's displayed index from the recipe book. */ public class DeleteCommand extends Command { public static final String COMMAND_WORD = "delete"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" + + ": Deletes the recipe identified by the index number shown in the displayed recipe list.\n" + "Parameters: INDEX (must be a positive integer)\n" + "Example: " + COMMAND_WORD + " 1"; - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_DELETE_RECIPE_SUCCESS = "Deleted Recipe: %1$s"; private final Index targetIndex; @@ -33,15 +33,15 @@ public DeleteCommand(Index targetIndex) { @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); + List lastShownList = model.getFilteredRecipeList(); if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_RECIPE_DISPLAYED_INDEX); } - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + Recipe recipeToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteRecipe(recipeToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_RECIPE_SUCCESS, recipeToDelete)); } @Override diff --git a/src/main/java/cookbuddy/logic/commands/DoneCommand.java b/src/main/java/cookbuddy/logic/commands/DoneCommand.java new file mode 100644 index 00000000000..9c9ea92c502 --- /dev/null +++ b/src/main/java/cookbuddy/logic/commands/DoneCommand.java @@ -0,0 +1,53 @@ +package cookbuddy.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import cookbuddy.commons.core.Messages; +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.exceptions.CommandException; +import cookbuddy.model.Model; +import cookbuddy.model.recipe.Recipe; + +/** + * Marks a recipe as done, identified using it's displayed index from the recipe book. + */ +public class DoneCommand extends Command { + + public static final String COMMAND_WORD = "done"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Marks the recipe identified by the index number shown in the displayed recipe list as done.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DONE_RECIPE_SUCCESS = "Completed Recipe: %1$s"; + + private final Index targetIndex; + + public DoneCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredRecipeList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_RECIPE_DISPLAYED_INDEX); + } + + Recipe recipeToDo = lastShownList.get(targetIndex.getZeroBased()); + model.attemptRecipe(recipeToDo); + return new CommandResult(String.format(MESSAGE_DONE_RECIPE_SUCCESS, recipeToDo.getName())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DoneCommand // instanceof handles nulls + && targetIndex.equals(((DoneCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/cookbuddy/logic/commands/DuplicateCommand.java b/src/main/java/cookbuddy/logic/commands/DuplicateCommand.java new file mode 100644 index 00000000000..a0f73edacfd --- /dev/null +++ b/src/main/java/cookbuddy/logic/commands/DuplicateCommand.java @@ -0,0 +1,71 @@ +package cookbuddy.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import cookbuddy.commons.core.Messages; +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.exceptions.CommandException; +import cookbuddy.model.Model; +import cookbuddy.model.recipe.Recipe; +import cookbuddy.model.recipe.attribute.Name; + +/** + * Adds a duplicate of the recipe identified using it's displayed index from the recipe book. + */ +public class DuplicateCommand extends Command { + + public static final String COMMAND_WORD = "duplicate"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Adds a duplicate of the recipe identified by the index number to the recipe list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_RECIPE_SUCCESS = "Recipe duplicated: %1$s"; + public static final String MESSAGE_DUPLICATE_RECIPE_FAIL = "This recipe has already been duplicated!"; + + private final Index targetIndex; + + public DuplicateCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredRecipeList(); + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_RECIPE_DISPLAYED_INDEX); + } + + Recipe recipeToDuplicate = lastShownList.get(targetIndex.getZeroBased()); + String name = recipeToDuplicate.getName().getName(); + String newName = "Duplicate of " + name; + Name nameOfDuplicate = new Name(newName); + ModifyCommand.EditRecipeDescriptor editRecipeDescriptor = new ModifyCommand.EditRecipeDescriptor(); + editRecipeDescriptor.setName(nameOfDuplicate); + Recipe duplicatedRecipe = ModifyCommand.createEditedRecipe(recipeToDuplicate, editRecipeDescriptor); + if (model.hasRecipe(duplicatedRecipe)) { + throw new CommandException(MESSAGE_DUPLICATE_RECIPE_FAIL); + } else { + model.addRecipe(duplicatedRecipe); + duplicatedRecipe.setTime(recipeToDuplicate.getPrepTime()); + if (recipeToDuplicate.getFavStatus().getfavStatus()) { + duplicatedRecipe.favRecipe(); + } + if (recipeToDuplicate.getDoneStatus().getDoneStatus()) { + duplicatedRecipe.attemptRecipe(); + } + return new CommandResult(String.format(MESSAGE_DELETE_RECIPE_SUCCESS, recipeToDuplicate)); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DuplicateCommand // instanceof handles nulls + && targetIndex.equals(((DuplicateCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/cookbuddy/logic/commands/ExitCommand.java similarity index 58% rename from src/main/java/seedu/address/logic/commands/ExitCommand.java rename to src/main/java/cookbuddy/logic/commands/ExitCommand.java index 3dd85a8ba90..641db2ede39 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/cookbuddy/logic/commands/ExitCommand.java @@ -1,6 +1,6 @@ -package seedu.address.logic.commands; +package cookbuddy.logic.commands; -import seedu.address.model.Model; +import cookbuddy.model.Model; /** * Terminates the program. @@ -9,7 +9,10 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String COMMAND_USAGE = COMMAND_WORD + ": Exits the CookBuddy application\n" + "Example: " + + COMMAND_WORD; + + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting CookBuddy as requested ..."; @Override public CommandResult execute(Model model) { diff --git a/src/main/java/cookbuddy/logic/commands/FavCommand.java b/src/main/java/cookbuddy/logic/commands/FavCommand.java new file mode 100644 index 00000000000..a5668f5b15e --- /dev/null +++ b/src/main/java/cookbuddy/logic/commands/FavCommand.java @@ -0,0 +1,53 @@ +package cookbuddy.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import cookbuddy.commons.core.Messages; +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.exceptions.CommandException; +import cookbuddy.model.Model; +import cookbuddy.model.recipe.Recipe; + +/** + * Favourites a recipe identified using it's displayed index from the recipe book. + */ +public class FavCommand extends Command { + + public static final String COMMAND_WORD = "fav"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Favourites the recipe identified by the index number shown in the displayed recipe list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_FAV_RECIPE_SUCCESS = "Favourited Recipe: %1$s"; + + private final Index targetIndex; + + public FavCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredRecipeList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_RECIPE_DISPLAYED_INDEX); + } + + Recipe recipeToFav = lastShownList.get(targetIndex.getZeroBased()); + model.favRecipe(recipeToFav); + return new CommandResult(String.format(MESSAGE_FAV_RECIPE_SUCCESS, recipeToFav.getName())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FavCommand // instanceof handles nulls + && targetIndex.equals(((FavCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/cookbuddy/logic/commands/FindCommand.java similarity index 53% rename from src/main/java/seedu/address/logic/commands/FindCommand.java rename to src/main/java/cookbuddy/logic/commands/FindCommand.java index d6b19b0a0de..6273b9d2586 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/cookbuddy/logic/commands/FindCommand.java @@ -1,36 +1,36 @@ -package seedu.address.logic.commands; +package cookbuddy.logic.commands; import static java.util.Objects.requireNonNull; -import seedu.address.commons.core.Messages; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import cookbuddy.commons.core.Messages; +import cookbuddy.model.Model; +import cookbuddy.model.recipe.ContainsKeywordsPredicate; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Finds and lists all recipes in recipe book whose name contains any of the argument keywords. * Keyword matching is case insensitive. */ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all recipes whose attribute contains 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"; + + "Parameters: attribute/ KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " n/ sandwich"; - private final NameContainsKeywordsPredicate predicate; + private final ContainsKeywordsPredicate predicate; - public FindCommand(NameContainsKeywordsPredicate predicate) { + public FindCommand(ContainsKeywordsPredicate predicate) { this.predicate = predicate; } @Override public CommandResult execute(Model model) { requireNonNull(model); - model.updateFilteredPersonList(predicate); + model.updateFilteredRecipeList(predicate); return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + String.format(Messages.MESSAGE_RECIPES_LISTED_OVERVIEW, model.getFilteredRecipeList().size())); } @Override diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/cookbuddy/logic/commands/HelpCommand.java similarity index 58% rename from src/main/java/seedu/address/logic/commands/HelpCommand.java rename to src/main/java/cookbuddy/logic/commands/HelpCommand.java index bf824f91bd0..9f62efa43d4 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/cookbuddy/logic/commands/HelpCommand.java @@ -1,6 +1,7 @@ -package seedu.address.logic.commands; +package cookbuddy.logic.commands; -import seedu.address.model.Model; +import cookbuddy.model.Model; +import cookbuddy.ui.UiManager; /** * Format full help instructions for every command for display. @@ -14,8 +15,20 @@ public class HelpCommand extends Command { public static final String SHOWING_HELP_MESSAGE = "Opened help window."; + private static String commandDescription = ""; + + public HelpCommand(String commandWord) { + commandDescription = commandWord; + } + + @Override public CommandResult execute(Model model) { + UiManager.setCommandDescription(commandDescription); return new CommandResult(SHOWING_HELP_MESSAGE, true, false); } + + public String getCommandWord () { + return commandDescription; + } } diff --git a/src/main/java/cookbuddy/logic/commands/ListCommand.java b/src/main/java/cookbuddy/logic/commands/ListCommand.java new file mode 100644 index 00000000000..1344fa63467 --- /dev/null +++ b/src/main/java/cookbuddy/logic/commands/ListCommand.java @@ -0,0 +1,27 @@ +package cookbuddy.logic.commands; + +import static cookbuddy.model.Model.PREDICATE_SHOW_ALL_RECIPES; +import static java.util.Objects.requireNonNull; + +import cookbuddy.model.Model; + +/** + * Lists all recipes in the recipe book to the user. + */ +public class ListCommand extends Command { + + public static final String COMMAND_WORD = "list"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Displays all the recipes in CookBuddy.\n" + + "Example: " + COMMAND_WORD; + + public static final String MESSAGE_SUCCESS = "Listed all available recipes."; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredRecipeList(PREDICATE_SHOW_ALL_RECIPES); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/cookbuddy/logic/commands/ModifyCommand.java b/src/main/java/cookbuddy/logic/commands/ModifyCommand.java new file mode 100644 index 00000000000..9c0887c94b6 --- /dev/null +++ b/src/main/java/cookbuddy/logic/commands/ModifyCommand.java @@ -0,0 +1,306 @@ +package cookbuddy.logic.commands; + +import static cookbuddy.logic.parser.CliSyntax.PREFIX_CALORIE; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_DIFFICULTY; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_IMAGEFILEPATH; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_INGREDIENTS; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_INSTRUCTIONS; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_NAME; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_RATING; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_SERVING; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_TAG; +import static cookbuddy.model.Model.PREDICATE_SHOW_ALL_RECIPES; +import static java.util.Objects.requireNonNull; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import cookbuddy.commons.core.Messages; +import cookbuddy.commons.core.index.Index; +import cookbuddy.commons.util.CollectionUtil; +import cookbuddy.logic.commands.exceptions.CommandException; +import cookbuddy.model.Model; +import cookbuddy.model.recipe.Recipe; +import cookbuddy.model.recipe.attribute.Calorie; +import cookbuddy.model.recipe.attribute.Difficulty; +import cookbuddy.model.recipe.attribute.IngredientList; +import cookbuddy.model.recipe.attribute.InstructionList; +import cookbuddy.model.recipe.attribute.Name; +import cookbuddy.model.recipe.attribute.Photograph; +import cookbuddy.model.recipe.attribute.Rating; +import cookbuddy.model.recipe.attribute.Serving; +import cookbuddy.model.recipe.attribute.Tag; +import cookbuddy.ui.UiManager; + +/** + * Edits the details of an existing recipe in the recipe book. + */ +public class ModifyCommand extends Command { + + public static final String COMMAND_WORD = "modify"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the recipe identified " + + "by the index number used in the displayed recipe list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_INGREDIENTS + "INGREDIENT 1, QUANTITY; INGREDIENT 2, QUANTITY] " + + "[" + PREFIX_INSTRUCTIONS + "INSTRUCTION 1; INSTRUCTION 2] " + + "[" + PREFIX_IMAGEFILEPATH + "PATH] " + + "[" + PREFIX_CALORIE + "CALORIES] " + + "[" + PREFIX_SERVING + "SERVING] " + + "[" + PREFIX_RATING + "RATING] " + + "[" + PREFIX_DIFFICULTY + "DIFFICULTY] " + + "[" + PREFIX_TAG + "TAG [TAG]...]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_INGREDIENTS + "toast, 2; eggs, 1 " + + PREFIX_INSTRUCTIONS + "put egg on toast; put bread on egg"; + + public static final String MESSAGE_EDIT_RECIPE_SUCCESS = "Edited Recipe: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_RECIPE = "This recipe already exists in the recipe book."; + + private final Index index; + private final EditRecipeDescriptor editRecipeDescriptor; + + /** + * @param index of the recipe in the filtered recipe list to edit + * @param editRecipeDescriptor details to edit the recipe with + */ + public ModifyCommand(Index index, EditRecipeDescriptor editRecipeDescriptor) { + requireNonNull(index); + requireNonNull(editRecipeDescriptor); + + this.index = index; + this.editRecipeDescriptor = new EditRecipeDescriptor(editRecipeDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredRecipeList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_RECIPE_DISPLAYED_INDEX); + } + + Recipe recipeToEdit = lastShownList.get(index.getZeroBased()); + Recipe editedRecipe = createEditedRecipe(recipeToEdit, editRecipeDescriptor); + + if (!recipeToEdit.isSameRecipe(editedRecipe) && model.hasRecipe(editedRecipe)) { + throw new CommandException(MESSAGE_DUPLICATE_RECIPE); + } + + model.setRecipe(recipeToEdit, editedRecipe); + model.updateFilteredRecipeList(PREDICATE_SHOW_ALL_RECIPES); + if (UiManager.getViewedRecipe() == recipeToEdit) { + UiManager.changeRecipe(editedRecipe); + } + return new CommandResult(String.format(MESSAGE_EDIT_RECIPE_SUCCESS, editedRecipe)); + } + + /** + * Creates and returns a {@code Recipe} with the details of {@code recipeToEdit} + * edited with {@code editRecipeDescriptor}. + */ + public static Recipe createEditedRecipe(Recipe recipeToEdit, EditRecipeDescriptor editRecipeDescriptor) { + assert recipeToEdit != null; + + Name updatedName = editRecipeDescriptor.getName().orElse(recipeToEdit.getName()); + IngredientList updatedIngredients = editRecipeDescriptor.getIngredients().orElse(recipeToEdit.getIngredients()); + InstructionList updatedInstructions = + editRecipeDescriptor.getInstructions().orElse(recipeToEdit.getInstructions()); + Photograph updatedImage = editRecipeDescriptor.getImageFilePath().orElse(recipeToEdit.getPhotograph()); + Calorie updatedCalorie = editRecipeDescriptor.getCalorie().orElse(recipeToEdit.getCalorie()); + Serving updatedServing = editRecipeDescriptor.getServing().orElse(recipeToEdit.getServing()); + Rating updatedRating = editRecipeDescriptor.getRating().orElse(recipeToEdit.getRating()); + Difficulty updatedDifficulty = editRecipeDescriptor.getDifficulty().orElse(recipeToEdit.getDifficulty()); + Set updatedTags = editRecipeDescriptor.getTags().orElse(recipeToEdit.getTags()); + + return new Recipe(updatedName, updatedIngredients, updatedInstructions, updatedImage, updatedCalorie, + updatedServing, + updatedRating, updatedDifficulty, updatedTags); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ModifyCommand)) { + return false; + } + + // state check + ModifyCommand e = (ModifyCommand) other; + return index.equals(e.index) + && editRecipeDescriptor.equals(e.editRecipeDescriptor); + } + + /** + * Stores the details to edit the recipe with. Each non-empty field value will replace the + * corresponding field value of the recipe. + */ + public static class EditRecipeDescriptor { + private Name name; + private IngredientList ingredients; + private InstructionList instructions; + private Photograph imageFilePath; + private Calorie calorie; + private Serving serving; + private Rating rating; + private Difficulty difficulty; + private Set tags; + + public EditRecipeDescriptor() { + } + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditRecipeDescriptor(EditRecipeDescriptor toCopy) { + setName(toCopy.name); + setIngredients(toCopy.ingredients); + setInstructions(toCopy.instructions); + setImageFilePath(toCopy.imageFilePath); + setCalorie(toCopy.calorie); + setServing(toCopy.serving); + setRating(toCopy.rating); + setDifficulty(toCopy.difficulty); + setTags(toCopy.tags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, ingredients, instructions, imageFilePath, calorie, serving, rating, + difficulty, tags); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public Optional getIngredients() { + return Optional.ofNullable(ingredients); + } + + public void setIngredients(IngredientList ingredients) { + this.ingredients = ingredients; + } + + public Optional getInstructions() { + return Optional.ofNullable(instructions); + } + + public void setInstructions(InstructionList instructions) { + this.instructions = instructions; + } + + public Optional getImageFilePath() { + return Optional.ofNullable(imageFilePath); + } + + public void setImageFilePath(Photograph imageFilePath) { + this.imageFilePath = imageFilePath; + } + + public void setCalorie(Calorie calorie) { + this.calorie = calorie; + } + + public Optional getCalorie() { + return (calorie != null) + ? Optional.of(calorie) + : Optional.empty(); + } + + public void setServing(Serving serving) { + this.serving = serving; + } + + public Optional getServing() { + return (serving != null) + ? Optional.of(serving) + : Optional.empty(); + } + + public void setRating(Rating rating) { + this.rating = rating; + } + + public Optional getRating() { + return (rating != null) + ? Optional.of(rating) + : Optional.empty(); + } + + + public void setDifficulty(Difficulty difficulty) { + this.difficulty = difficulty; + } + + public Optional getDifficulty() { + return (difficulty != null) ? Optional.of(difficulty) : Optional.empty(); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) + ? new HashSet<>(tags) + : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) + ? Optional.of(Collections.unmodifiableSet(tags)) + : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditRecipeDescriptor)) { + return false; + } + + // state check + EditRecipeDescriptor e = (EditRecipeDescriptor) other; + + return getName().equals(e.getName()) + && getIngredients().equals(e.getIngredients()) + && getInstructions().equals(e.getInstructions()) + // && getImageFilePath().equals(e.getImageFilePath()) + && getCalorie().equals(e.getCalorie()) + && getServing().equals(e.getServing()) + && getRating().equals(e.getRating()) + && getDifficulty().equals(e.getDifficulty()) + && getTags().equals(e.getTags()); + } + } +} diff --git a/src/main/java/cookbuddy/logic/commands/NewCommand.java b/src/main/java/cookbuddy/logic/commands/NewCommand.java new file mode 100644 index 00000000000..fbcdd4c31c8 --- /dev/null +++ b/src/main/java/cookbuddy/logic/commands/NewCommand.java @@ -0,0 +1,78 @@ +package cookbuddy.logic.commands; + +import static cookbuddy.logic.parser.CliSyntax.PREFIX_CALORIE; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_DIFFICULTY; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_IMAGEFILEPATH; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_INGREDIENTS; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_INSTRUCTIONS; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_NAME; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_RATING; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_SERVING; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_TAG; +import static java.util.Objects.requireNonNull; + +import cookbuddy.logic.commands.exceptions.CommandException; +import cookbuddy.model.Model; +import cookbuddy.model.recipe.Recipe; + +/** + * Adds a recipe to the recipe book. + */ +public class NewCommand extends Command { + + public static final String COMMAND_WORD = "new"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a recipe to the recipe book.\n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_INGREDIENTS + "INGREDIENT 1, QUANTITY; INGREDIENT 2, QUANTITY... " + + PREFIX_INSTRUCTIONS + "INSTRUCTION 1, INSTRUCTION 2... " + + "[" + PREFIX_IMAGEFILEPATH + "PATH] " + + "[" + PREFIX_CALORIE + "CALORIES] " + + "[" + PREFIX_SERVING + "SERVING SIZE] " + + "[" + PREFIX_RATING + "RATING] " + + "[" + PREFIX_DIFFICULTY + "DIFFICULTY] " + + "[" + PREFIX_TAG + "TAG [TAG]...]\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "Ham Sandwich " + + PREFIX_INGREDIENTS + "bread, 2 slices; ham, 1 slice " + + PREFIX_INSTRUCTIONS + "put ham between bread; eat sandwich " + + PREFIX_IMAGEFILEPATH + "absolute/path/to/image " + + PREFIX_CALORIE + "169 " + + PREFIX_SERVING + "2 " + + PREFIX_RATING + "4 " + + PREFIX_DIFFICULTY + "2 " + + PREFIX_TAG + "breakfast"; + + public static final String MESSAGE_SUCCESS = "New recipe added: %1$s"; + public static final String MESSAGE_DUPLICATE_RECIPE = "This recipe already exists in the recipe book"; + + private final Recipe toAdd; + + /** + * Creates an NewCommand to add the specified {@code Recipe} + */ + public NewCommand(Recipe recipe) { + requireNonNull(recipe); + toAdd = recipe; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasRecipe(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_RECIPE); + } + + model.addRecipe(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof NewCommand // instanceof handles nulls + && toAdd.equals(((NewCommand) other).toAdd)); + } +} diff --git a/src/main/java/cookbuddy/logic/commands/RandomCommand.java b/src/main/java/cookbuddy/logic/commands/RandomCommand.java new file mode 100644 index 00000000000..7f7287b9aa9 --- /dev/null +++ b/src/main/java/cookbuddy/logic/commands/RandomCommand.java @@ -0,0 +1,70 @@ +package cookbuddy.logic.commands; + +import static java.lang.Math.random; +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import cookbuddy.commons.core.Messages; +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.exceptions.CommandException; +import cookbuddy.model.Model; +import cookbuddy.model.recipe.Recipe; +import cookbuddy.ui.UiManager; + + +/** + * Displays a random recipe from the recipe book to the user. + */ +public class RandomCommand extends Command { + + public static final String COMMAND_WORD = "random"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Displays a random recipe \n" + + "Example: " + COMMAND_WORD; + + + public static final String MESSAGE_VIEW_RECIPE_SUCCESS = "Viewing Recipe: %1$s"; + + private static int maxVal = 1; + private final Index targetIndex; + + public RandomCommand() { + double randomVal = random(); + randomVal *= maxVal; + int randomNum = (int) randomVal; + Index randomIndex = Index.fromZeroBased(randomNum); + this.targetIndex = randomIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredRecipeList(); + maxVal = (int) model.count(); + + if (maxVal == 0) { + throw new CommandException(Messages.MESSAGE_EMPTY_RECIPE_BOOK); + } + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_RECIPE_DISPLAYED_INDEX); + } + + Recipe recipeToView = lastShownList.get(targetIndex.getZeroBased()); + UiManager.changeRecipe(recipeToView); + return new CommandResult(String.format(MESSAGE_VIEW_RECIPE_SUCCESS, recipeToView.getName())); + } + + public Index getTargetIndex() { + return targetIndex; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RandomCommand // instanceof handles nulls + && targetIndex.equals(((RandomCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/cookbuddy/logic/commands/ResetCommand.java b/src/main/java/cookbuddy/logic/commands/ResetCommand.java new file mode 100644 index 00000000000..00797a1416e --- /dev/null +++ b/src/main/java/cookbuddy/logic/commands/ResetCommand.java @@ -0,0 +1,30 @@ +package cookbuddy.logic.commands; + +import static java.util.Objects.requireNonNull; + +import cookbuddy.model.Model; +import cookbuddy.model.RecipeBook; +import cookbuddy.ui.UiManager; + +/** + * Clears the recipe book. + */ +public class ResetCommand extends Command { + + public static final String COMMAND_WORD = "reset"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes all recipes from CookBuddy.\n" + + "Example: " + COMMAND_WORD; + public static final String MESSAGE_SUCCESS = "The recipe book has been cleared!"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.setRecipeBook(new RecipeBook()); + if (model.getFilteredRecipeList().size() > 0) { + UiManager.removeRecipe(); + } + + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/cookbuddy/logic/commands/TimeCommand.java b/src/main/java/cookbuddy/logic/commands/TimeCommand.java new file mode 100644 index 00000000000..7eaa80f8911 --- /dev/null +++ b/src/main/java/cookbuddy/logic/commands/TimeCommand.java @@ -0,0 +1,58 @@ +package cookbuddy.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import cookbuddy.commons.core.Messages; +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.exceptions.CommandException; +import cookbuddy.model.Model; +import cookbuddy.model.recipe.Recipe; +import cookbuddy.model.recipe.attribute.Time; + +/** + * Adds a time to a recipe, identified using it's displayed index from the recipe book. + */ +public class TimeCommand extends Command { + + public static final String COMMAND_WORD = "time"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Provides a prep time to the recipe identified by the index number shown in the displayed recipe list" + + ".\n" + + "Parameters: INDEX (must be a positive integer), TIME (hh:MM:ss) *minutes and seconds are optional*\n" + + "Example: " + COMMAND_WORD + " 1" + " 00:59:00 "; + + public static final String MESSAGE_TIME_RECIPE_SUCCESS = "Time for Recipe: %1$s %2$s"; + + private final Index targetIndex; + private final Time prepTime; + + public TimeCommand(Index targetIndex, Time prepTime) { + this.targetIndex = targetIndex; + this.prepTime = prepTime; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredRecipeList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_RECIPE_DISPLAYED_INDEX); + } + + Recipe recipeToSet = lastShownList.get(targetIndex.getZeroBased()); + model.setTime(recipeToSet, prepTime); + return new CommandResult(String.format(MESSAGE_TIME_RECIPE_SUCCESS, recipeToSet.getName(), + recipeToSet.getPrepTime())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TimeCommand // instanceof handles nulls + && targetIndex.equals(((TimeCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/cookbuddy/logic/commands/UnFavCommand.java b/src/main/java/cookbuddy/logic/commands/UnFavCommand.java new file mode 100644 index 00000000000..9fd503570c5 --- /dev/null +++ b/src/main/java/cookbuddy/logic/commands/UnFavCommand.java @@ -0,0 +1,53 @@ +package cookbuddy.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import cookbuddy.commons.core.Messages; +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.exceptions.CommandException; +import cookbuddy.model.Model; +import cookbuddy.model.recipe.Recipe; + +/** + * Un-favourites a recipe identified using it's displayed index from the recipe book. + */ +public class UnFavCommand extends Command { + + public static final String COMMAND_WORD = "unfav"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Un-favourites the recipe identified by the index number shown in the displayed recipe list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_UNFAV_RECIPE_SUCCESS = "Un-Favourited Recipe: %1$s"; + + private final Index targetIndex; + + public UnFavCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredRecipeList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_RECIPE_DISPLAYED_INDEX); + } + + Recipe recipeToUnFav = lastShownList.get(targetIndex.getZeroBased()); + model.unFavRecipe(recipeToUnFav); + return new CommandResult(String.format(MESSAGE_UNFAV_RECIPE_SUCCESS, recipeToUnFav.getName())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UnFavCommand // instanceof handles nulls + && targetIndex.equals(((UnFavCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/cookbuddy/logic/commands/UndoCommand.java b/src/main/java/cookbuddy/logic/commands/UndoCommand.java new file mode 100644 index 00000000000..c914569cb00 --- /dev/null +++ b/src/main/java/cookbuddy/logic/commands/UndoCommand.java @@ -0,0 +1,53 @@ +package cookbuddy.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import cookbuddy.commons.core.Messages; +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.exceptions.CommandException; +import cookbuddy.model.Model; +import cookbuddy.model.recipe.Recipe; + +/** + * Un-Marks a recipe as done, identified using it's displayed index from the recipe book. + */ +public class UndoCommand extends Command { + + public static final String COMMAND_WORD = "undo"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Un-Marks the recipe identified by the index number shown in the displayed recipe list as done.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_UNDONE_RECIPE_SUCCESS = "Un-Marked Recipe: %1$s"; + + private final Index targetIndex; + + public UndoCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredRecipeList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_RECIPE_DISPLAYED_INDEX); + } + + Recipe recipeToDo = lastShownList.get(targetIndex.getZeroBased()); + model.unAttemptRecipe(recipeToDo); + return new CommandResult(String.format(MESSAGE_UNDONE_RECIPE_SUCCESS, recipeToDo.getName())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UndoCommand // instanceof handles nulls + && targetIndex.equals(((UndoCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/cookbuddy/logic/commands/ViewCommand.java b/src/main/java/cookbuddy/logic/commands/ViewCommand.java new file mode 100644 index 00000000000..792f8c1fe0d --- /dev/null +++ b/src/main/java/cookbuddy/logic/commands/ViewCommand.java @@ -0,0 +1,56 @@ +package cookbuddy.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import cookbuddy.commons.core.Messages; +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.exceptions.CommandException; +import cookbuddy.model.Model; +import cookbuddy.model.recipe.Recipe; +import cookbuddy.ui.UiManager; + + +/** + * Lists all recipes in the recipe book to the user. + */ +public class ViewCommand extends Command { + + public static final String COMMAND_WORD = "view"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Views the recipe identified by the index number shown in the displayed recipe list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + + public static final String MESSAGE_VIEW_RECIPE_SUCCESS = "Viewing Recipe: %1$s"; + + private final Index targetIndex; + + public ViewCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredRecipeList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_RECIPE_DISPLAYED_INDEX); + } + + Recipe recipeToView = lastShownList.get(targetIndex.getZeroBased()); + UiManager.changeRecipe(recipeToView); + return new CommandResult(String.format(MESSAGE_VIEW_RECIPE_SUCCESS, recipeToView.getName())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ViewCommand // instanceof handles nulls + && targetIndex.equals(((ViewCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/cookbuddy/logic/commands/exceptions/CommandException.java similarity index 85% rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java rename to src/main/java/cookbuddy/logic/commands/exceptions/CommandException.java index a16bd14f2cd..75c9feba627 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/cookbuddy/logic/commands/exceptions/CommandException.java @@ -1,8 +1,9 @@ -package seedu.address.logic.commands.exceptions; +package cookbuddy.logic.commands.exceptions; /** * Represents an error which occurs during execution of a {@link Command}. */ +@SuppressWarnings("serial") public class CommandException extends Exception { public CommandException(String message) { super(message); diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/cookbuddy/logic/parser/ArgumentMultimap.java similarity index 98% rename from src/main/java/seedu/address/logic/parser/ArgumentMultimap.java rename to src/main/java/cookbuddy/logic/parser/ArgumentMultimap.java index 954c8e18f8e..4861f2888e1 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/cookbuddy/logic/parser/ArgumentMultimap.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package cookbuddy.logic.parser; import java.util.ArrayList; import java.util.HashMap; diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/cookbuddy/logic/parser/ArgumentTokenizer.java similarity index 99% rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java rename to src/main/java/cookbuddy/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..5a9c6f6a6fc 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/cookbuddy/logic/parser/ArgumentTokenizer.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package cookbuddy.logic.parser; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/cookbuddy/logic/parser/CliSyntax.java b/src/main/java/cookbuddy/logic/parser/CliSyntax.java new file mode 100644 index 00000000000..a2ebfc0d56a --- /dev/null +++ b/src/main/java/cookbuddy/logic/parser/CliSyntax.java @@ -0,0 +1,18 @@ +package cookbuddy.logic.parser; + +/** + * Contains Command Line Interface (CLI) syntax definitions common to multiple commands + */ +public class CliSyntax { + + /* Prefix definitions */ + public static final Prefix PREFIX_NAME = new Prefix("n/"); + public static final Prefix PREFIX_INGREDIENTS = new Prefix("ing/"); + public static final Prefix PREFIX_INSTRUCTIONS = new Prefix("ins/"); + public static final Prefix PREFIX_IMAGEFILEPATH = new Prefix("p/"); + public static final Prefix PREFIX_CALORIE = new Prefix("cal/"); + public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_RATING = new Prefix("r/"); + public static final Prefix PREFIX_SERVING = new Prefix("s/"); + public static final Prefix PREFIX_DIFFICULTY = new Prefix("d/"); +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/cookbuddy/logic/parser/DeleteCommandParser.java similarity index 58% rename from src/main/java/seedu/address/logic/parser/DeleteCommandParser.java rename to src/main/java/cookbuddy/logic/parser/DeleteCommandParser.java index 522b93081cc..e13f67f01d8 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/cookbuddy/logic/parser/DeleteCommandParser.java @@ -1,10 +1,10 @@ -package seedu.address.logic.parser; +package cookbuddy.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static cookbuddy.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.DeleteCommand; +import cookbuddy.logic.parser.exceptions.ParseException; /** * Parses input arguments and creates a new DeleteCommand object @@ -14,6 +14,7 @@ public class DeleteCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the DeleteCommand * and returns a DeleteCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public DeleteCommand parse(String args) throws ParseException { @@ -21,8 +22,8 @@ public DeleteCommand parse(String args) throws ParseException { Index index = ParserUtil.parseIndex(args); return new DeleteCommand(index); } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getLocalizedMessage()) + + "\nFor a command summary, type \"help delete\""); } } diff --git a/src/main/java/cookbuddy/logic/parser/DoneCommandParser.java b/src/main/java/cookbuddy/logic/parser/DoneCommandParser.java new file mode 100644 index 00000000000..e256d086069 --- /dev/null +++ b/src/main/java/cookbuddy/logic/parser/DoneCommandParser.java @@ -0,0 +1,31 @@ +package cookbuddy.logic.parser; + +import static cookbuddy.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.DoneCommand; +import cookbuddy.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new DoneCommand object + */ +public class DoneCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DoneCommand + * and returns a DoneCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DoneCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DoneCommand(index); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getLocalizedMessage()) + + "\nFor a command summary, type \"help done\""); + } + } + +} diff --git a/src/main/java/cookbuddy/logic/parser/DuplicateCommandParser.java b/src/main/java/cookbuddy/logic/parser/DuplicateCommandParser.java new file mode 100644 index 00000000000..aec9d7059e1 --- /dev/null +++ b/src/main/java/cookbuddy/logic/parser/DuplicateCommandParser.java @@ -0,0 +1,30 @@ +package cookbuddy.logic.parser; + +import static cookbuddy.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.DuplicateCommand; +import cookbuddy.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DuplicateCommand object + */ +public class DuplicateCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DuplicateCommand + * and returns a DuplicateCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DuplicateCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DuplicateCommand(index); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getLocalizedMessage()) + + "\nFor a command summary, type \"help duplicate\""); + } + } + +} diff --git a/src/main/java/cookbuddy/logic/parser/FavCommandParser.java b/src/main/java/cookbuddy/logic/parser/FavCommandParser.java new file mode 100644 index 00000000000..efcae3931e7 --- /dev/null +++ b/src/main/java/cookbuddy/logic/parser/FavCommandParser.java @@ -0,0 +1,30 @@ +package cookbuddy.logic.parser; + +import static cookbuddy.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.FavCommand; +import cookbuddy.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new FavCommand object + */ +public class FavCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FavCommand + * and returns a FavCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public FavCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new FavCommand(index); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getLocalizedMessage()) + + "\nFor a command summary, type \"help fav\""); + } + } + +} diff --git a/src/main/java/cookbuddy/logic/parser/FindCommandParser.java b/src/main/java/cookbuddy/logic/parser/FindCommandParser.java new file mode 100644 index 00000000000..64e63d5e09a --- /dev/null +++ b/src/main/java/cookbuddy/logic/parser/FindCommandParser.java @@ -0,0 +1,75 @@ +package cookbuddy.logic.parser; + +import static cookbuddy.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_INGREDIENTS; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_INSTRUCTIONS; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.Arrays; + +import cookbuddy.logic.commands.FindCommand; +import cookbuddy.logic.parser.exceptions.ParseException; +import cookbuddy.model.recipe.IngredientContainsKeywordsPredicate; +import cookbuddy.model.recipe.InstructionContainsKeywordsPredicate; +import cookbuddy.model.recipe.NameContainsKeywordsPredicate; +import cookbuddy.model.recipe.attribute.Name; + +/** + * Parses input arguments and creates a new FindCommand object + */ +public class FindCommandParser implements Parser { + + /** + * Returns true if only one of the prefixes contains a value in the given + * {@code ArgumentMultimap}. + */ + public static boolean isSinglePrefixPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + long number = Arrays.stream(prefixes).filter(prefix -> argumentMultimap.getValue(prefix).isPresent()).count(); + + return number == 1 ? true : false; + } + + /** + * 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 FindCommand parse(String args) throws ParseException { + ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_INGREDIENTS, + PREFIX_INSTRUCTIONS); + + // TODO: allow finding via multiple prefixes + if (!isSinglePrefixPresent(argumentMultimap, PREFIX_NAME, PREFIX_INGREDIENTS, PREFIX_INSTRUCTIONS)) { + throw new ParseException("Please search for 1 attribute at a time!"); + } + + String[] keywords; + + if (argumentMultimap.getValue(PREFIX_NAME).isPresent()) { + if (argumentMultimap.getValue(PREFIX_NAME).get().isEmpty()) { + throw new ParseException(Name.MESSAGE_CONSTRAINTS); + } + + keywords = argumentMultimap.getValue(PREFIX_NAME).get().split("\\s+"); + return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(keywords))); + + } else if (argumentMultimap.getValue(PREFIX_INGREDIENTS).isPresent()) { + if (argumentMultimap.getValue(PREFIX_INGREDIENTS).get().isEmpty()) { + throw new ParseException("Enter ingredients to search for."); + } + + keywords = argumentMultimap.getValue(PREFIX_INGREDIENTS).get().split("\\s+"); + return new FindCommand(new IngredientContainsKeywordsPredicate(Arrays.asList(keywords))); + } else if (argumentMultimap.getValue(PREFIX_INSTRUCTIONS).isPresent()) { + if (argumentMultimap.getValue(PREFIX_INSTRUCTIONS).get().isEmpty()) { + throw new ParseException("Enter instruction words to search for."); + } + + keywords = argumentMultimap.getValue(PREFIX_INSTRUCTIONS).get().split("\\s+"); + return new FindCommand(new InstructionContainsKeywordsPredicate(Arrays.asList(keywords))); + } + + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + +} diff --git a/src/main/java/cookbuddy/logic/parser/HelpCommandParser.java b/src/main/java/cookbuddy/logic/parser/HelpCommandParser.java new file mode 100644 index 00000000000..03cac42a356 --- /dev/null +++ b/src/main/java/cookbuddy/logic/parser/HelpCommandParser.java @@ -0,0 +1,104 @@ +package cookbuddy.logic.parser; + +import static cookbuddy.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import cookbuddy.logic.commands.CountCommand; +import cookbuddy.logic.commands.DeleteCommand; +import cookbuddy.logic.commands.DoneCommand; +import cookbuddy.logic.commands.DuplicateCommand; +import cookbuddy.logic.commands.ExitCommand; +import cookbuddy.logic.commands.FavCommand; +import cookbuddy.logic.commands.FindCommand; +import cookbuddy.logic.commands.HelpCommand; +import cookbuddy.logic.commands.ListCommand; +import cookbuddy.logic.commands.ModifyCommand; +import cookbuddy.logic.commands.NewCommand; +import cookbuddy.logic.commands.RandomCommand; +import cookbuddy.logic.commands.ResetCommand; +import cookbuddy.logic.commands.TimeCommand; +import cookbuddy.logic.commands.UnFavCommand; +import cookbuddy.logic.commands.UndoCommand; +import cookbuddy.logic.commands.ViewCommand; +import cookbuddy.logic.parser.exceptions.ParseException; +import cookbuddy.ui.HelpWindow; + +/** + * Parses user input. + */ + +public class HelpCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the HelpCommand + * and returns a HelpCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public HelpCommand parse(String args) throws ParseException { + try { + String command = ParserUtil.parseHelp(args); + String preface = "Showing usage for the " + command + " command:\n\n"; + String ending = "\n\nFor more information, you may " + HelpWindow.HELP_MESSAGE.toLowerCase(); + + switch (command) { + case NewCommand.COMMAND_WORD: + return new HelpCommand(preface + NewCommand.MESSAGE_USAGE + ending); + + case ModifyCommand.COMMAND_WORD: + return new HelpCommand(preface + ModifyCommand.MESSAGE_USAGE + ending); + + case DuplicateCommand.COMMAND_WORD: + return new HelpCommand(preface + DuplicateCommand.MESSAGE_USAGE + ending); + + case DeleteCommand.COMMAND_WORD: + return new HelpCommand(preface + DeleteCommand.MESSAGE_USAGE + ending); + + case DoneCommand.COMMAND_WORD: + return new HelpCommand(preface + DoneCommand.MESSAGE_USAGE + ending); + + case UndoCommand.COMMAND_WORD: + return new HelpCommand(preface + UndoCommand.MESSAGE_USAGE + ending); + + case FavCommand.COMMAND_WORD: + return new HelpCommand(preface + FavCommand.MESSAGE_USAGE + ending); + + case UnFavCommand.COMMAND_WORD: + return new HelpCommand(preface + UnFavCommand.MESSAGE_USAGE + ending); + + case ResetCommand.COMMAND_WORD: + return new HelpCommand(preface + ResetCommand.MESSAGE_USAGE + ending); + + case ViewCommand.COMMAND_WORD: + return new HelpCommand(preface + ViewCommand.MESSAGE_USAGE + ending); + + case FindCommand.COMMAND_WORD: + return new HelpCommand(preface + FindCommand.MESSAGE_USAGE + ending); + + case ListCommand.COMMAND_WORD: + return new HelpCommand(preface + ListCommand.MESSAGE_USAGE + ending); + + case RandomCommand.COMMAND_WORD: + return new HelpCommand(preface + RandomCommand.MESSAGE_USAGE + ending); + + case TimeCommand.COMMAND_WORD: + return new HelpCommand(preface + TimeCommand.MESSAGE_USAGE + ending); + + case CountCommand.COMMAND_WORD: + return new HelpCommand(preface + CountCommand.MESSAGE_USAGE + ending); + + case ExitCommand.COMMAND_WORD: + return new HelpCommand(preface + ExitCommand.COMMAND_USAGE + ending); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand("You are already using the help command"); + + default: + throw new ParseException("Invalid Command chosen!"); + } + + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getLocalizedMessage())); + } + } + +} diff --git a/src/main/java/cookbuddy/logic/parser/ModifyCommandParser.java b/src/main/java/cookbuddy/logic/parser/ModifyCommandParser.java new file mode 100644 index 00000000000..56630c79825 --- /dev/null +++ b/src/main/java/cookbuddy/logic/parser/ModifyCommandParser.java @@ -0,0 +1,111 @@ +package cookbuddy.logic.parser; + +import static cookbuddy.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_CALORIE; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_DIFFICULTY; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_IMAGEFILEPATH; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_INGREDIENTS; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_INSTRUCTIONS; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_NAME; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_RATING; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_SERVING; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_TAG; +import static java.util.Objects.requireNonNull; + +import java.util.Optional; +import java.util.Set; + +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.ModifyCommand; +import cookbuddy.logic.parser.exceptions.ParseException; +import cookbuddy.model.recipe.attribute.Tag; + + +/** + * Parses input arguments and creates a new ModifyCommand object + */ +public class ModifyCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ModifyCommand + * and returns an ModifyCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ModifyCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_INGREDIENTS, PREFIX_INSTRUCTIONS, PREFIX_IMAGEFILEPATH, + PREFIX_CALORIE, PREFIX_SERVING, PREFIX_RATING, PREFIX_DIFFICULTY, PREFIX_TAG); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getLocalizedMessage()) + + "\nFor a command summary, type \"help modify\""); + } + + ModifyCommand.EditRecipeDescriptor editRecipeDescriptor = new ModifyCommand.EditRecipeDescriptor(); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editRecipeDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_INGREDIENTS).isPresent()) { + editRecipeDescriptor.setIngredients( + ParserUtil.parseIngredients(argMultimap.getValue(PREFIX_INGREDIENTS).get())); + } + if (argMultimap.getValue(PREFIX_INSTRUCTIONS).isPresent()) { + editRecipeDescriptor.setInstructions( + ParserUtil.parseInstructions(argMultimap.getValue(PREFIX_INSTRUCTIONS).get())); + } + + if (argMultimap.getValue(PREFIX_IMAGEFILEPATH).isPresent()) { + editRecipeDescriptor + .setImageFilePath(ParserUtil.parsePhotoFilePath(argMultimap.getValue(PREFIX_IMAGEFILEPATH).get())); + } + + if (argMultimap.getValue(PREFIX_SERVING).isPresent()) { + editRecipeDescriptor.setServing( + ParserUtil.parseServing(argMultimap.getValue(PREFIX_SERVING).get())); + } + + if (argMultimap.getValue(PREFIX_CALORIE).isPresent()) { + editRecipeDescriptor.setCalorie( + ParserUtil.parseCalorie(argMultimap.getValue(PREFIX_CALORIE).get())); + } + + if (argMultimap.getValue(PREFIX_RATING).isPresent()) { + editRecipeDescriptor.setRating( + ParserUtil.parseRating(argMultimap.getValue(PREFIX_RATING).get())); + } + + if (argMultimap.getValue(PREFIX_DIFFICULTY).isPresent()) { + editRecipeDescriptor.setDifficulty( + ParserUtil.parseDifficulty(argMultimap.getValue(PREFIX_DIFFICULTY).get())); + } + parseTagsForEdit(argMultimap.getValue(PREFIX_TAG)).ifPresent(editRecipeDescriptor::setTags); + + if (!editRecipeDescriptor.isAnyFieldEdited()) { + throw new ParseException(ModifyCommand.MESSAGE_NOT_EDITED); + } + + return new ModifyCommand(index, editRecipeDescriptor); + } + + /** + * Parses {@code Optional tags} into a {@code Set} if {@code tags} is non-empty. + * If {@code tags} is an empty string, it will be parsed into a + * {@code Set} containing zero tags. + */ + private Optional> parseTagsForEdit(Optional tags) throws ParseException { + assert tags != null; + + if (tags.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(ParserUtil.parseTags(tags)); + } + +} diff --git a/src/main/java/cookbuddy/logic/parser/NewCommandParser.java b/src/main/java/cookbuddy/logic/parser/NewCommandParser.java new file mode 100644 index 00000000000..f7bc4ac5c74 --- /dev/null +++ b/src/main/java/cookbuddy/logic/parser/NewCommandParser.java @@ -0,0 +1,75 @@ +package cookbuddy.logic.parser; + +import static cookbuddy.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_CALORIE; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_DIFFICULTY; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_IMAGEFILEPATH; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_INGREDIENTS; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_INSTRUCTIONS; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_NAME; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_RATING; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_SERVING; +import static cookbuddy.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Set; +import java.util.stream.Stream; + +import cookbuddy.logic.commands.NewCommand; +import cookbuddy.logic.parser.exceptions.ParseException; +import cookbuddy.model.recipe.Recipe; +import cookbuddy.model.recipe.attribute.Calorie; +import cookbuddy.model.recipe.attribute.Difficulty; +import cookbuddy.model.recipe.attribute.IngredientList; +import cookbuddy.model.recipe.attribute.InstructionList; +import cookbuddy.model.recipe.attribute.Name; +import cookbuddy.model.recipe.attribute.Photograph; +import cookbuddy.model.recipe.attribute.Rating; +import cookbuddy.model.recipe.attribute.Serving; +import cookbuddy.model.recipe.attribute.Tag; + +/** + * Parses input arguments and creates a new NewCommand object + */ +public class NewCommandParser implements Parser { + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values + * in the given {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Parses the given {@code String} of arguments in the context of the NewCommand + * and returns an NewCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public NewCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_INGREDIENTS, + PREFIX_INSTRUCTIONS, PREFIX_IMAGEFILEPATH, PREFIX_CALORIE, PREFIX_SERVING, PREFIX_RATING, + PREFIX_DIFFICULTY, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_INGREDIENTS, PREFIX_INSTRUCTIONS) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, NewCommand.MESSAGE_USAGE)); + } + + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + IngredientList ingredients = ParserUtil.parseIngredients(argMultimap.getValue(PREFIX_INGREDIENTS).get()); + InstructionList instructions = ParserUtil.parseInstructions(argMultimap.getValue(PREFIX_INSTRUCTIONS).get()); + Photograph photograph = ParserUtil.parsePhotoFilePath(argMultimap.getValue(PREFIX_IMAGEFILEPATH) + .orElse(Photograph.IMAGE_UTIL.PLACEHOLDER_IMAGE_PATH_STRING)); + Calorie calorie = ParserUtil.parseCalorie(argMultimap.getValue(PREFIX_CALORIE).orElse("0")); + Serving serving = ParserUtil.parseServing(argMultimap.getValue(PREFIX_SERVING).orElse("1")); + Rating rating = ParserUtil.parseRating(argMultimap.getValue(PREFIX_RATING).orElse("0")); + Difficulty difficulty = ParserUtil.parseDifficulty(argMultimap.getValue(PREFIX_DIFFICULTY).orElse("0")); + Set tagList = ParserUtil.parseTags(argMultimap.getValue(PREFIX_TAG)); + + Recipe recipe = new Recipe(name, ingredients, instructions, photograph, calorie, serving, rating, difficulty, + tagList); + + return new NewCommand(recipe); + } +} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/cookbuddy/logic/parser/Parser.java similarity index 72% rename from src/main/java/seedu/address/logic/parser/Parser.java rename to src/main/java/cookbuddy/logic/parser/Parser.java index d6551ad8e3f..f239b220150 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/cookbuddy/logic/parser/Parser.java @@ -1,7 +1,7 @@ -package seedu.address.logic.parser; +package cookbuddy.logic.parser; -import seedu.address.logic.commands.Command; -import seedu.address.logic.parser.exceptions.ParseException; +import cookbuddy.logic.commands.Command; +import cookbuddy.logic.parser.exceptions.ParseException; /** * Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}. diff --git a/src/main/java/cookbuddy/logic/parser/ParserUtil.java b/src/main/java/cookbuddy/logic/parser/ParserUtil.java new file mode 100644 index 00000000000..6ff84cd9107 --- /dev/null +++ b/src/main/java/cookbuddy/logic/parser/ParserUtil.java @@ -0,0 +1,305 @@ +package cookbuddy.logic.parser; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import cookbuddy.commons.core.index.Index; +import cookbuddy.commons.util.FileUtil; +import cookbuddy.commons.util.StringUtil; +import cookbuddy.logic.parser.exceptions.ParseException; +import cookbuddy.model.recipe.Recipe; +import cookbuddy.model.recipe.attribute.Calorie; +import cookbuddy.model.recipe.attribute.Difficulty; +import cookbuddy.model.recipe.attribute.Ingredient; +import cookbuddy.model.recipe.attribute.IngredientList; +import cookbuddy.model.recipe.attribute.Instruction; +import cookbuddy.model.recipe.attribute.InstructionList; +import cookbuddy.model.recipe.attribute.Name; +import cookbuddy.model.recipe.attribute.Photograph; +import cookbuddy.model.recipe.attribute.Rating; +import cookbuddy.model.recipe.attribute.Serving; +import cookbuddy.model.recipe.attribute.Tag; +import cookbuddy.model.recipe.attribute.Time; + +/** + * Contains utility methods used for parsing strings in the various *Parser + * classes. + */ +public class ParserUtil { + + public static final String MESSAGE_INVALID_INDEX = "Index must be a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_FILEPATH = Photograph.IMAGE_UTIL.messageConstraints; + + /** + * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading + * and trailing whitespaces will be trimmed. + * + * @throws ParseException if the specified index is invalid (not non-zero + * unsigned integer). + */ + public static Index parseIndex(String oneBasedIndex) throws ParseException { + String trimmedIndex = oneBasedIndex.trim(); + if (trimmedIndex.equals("")) { + throw new ParseException("No index has been provided for the command!"); + } + if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { + throw new ParseException(MESSAGE_INVALID_INDEX); + } + return Index.fromOneBased(Integer.parseInt(trimmedIndex)); + } + + /** + * Parses a {@code String name} into a {@code Name}. Leading and trailing + * whitespaces will be trimmed. + * + * @throws ParseException if the given {@code name} is invalid. + */ + public static Name parseName(String name) throws ParseException { + requireNonNull(name); + String trimmedName = name.trim(); + if (!Name.isValidName(trimmedName)) { + throw new ParseException(Name.MESSAGE_CONSTRAINTS); + } + return new Name(trimmedName); + } + + /** + * Parses {@code ingredientString} into an {@link IngredientList}. Leading and + * trailing whitespaces will be trimmed. + * + * @throws ParseException if {@code ingredientString} is invalid + */ + public static IngredientList parseIngredients(String ingredientString) throws ParseException { + requireNonNull(ingredientString); + + if (ingredientString.isBlank()) { + throw new ParseException(Ingredient.MESSAGE_CONSTRAINTS); + } + + try { + List ingredientList = Stream.of(ingredientString.trim().split(";")) + .map(String::trim).map(Ingredient::new).collect(Collectors.toList()); + + if (ingredientList.isEmpty()) { + throw new ParseException(Ingredient.MESSAGE_CONSTRAINTS); + } + return new IngredientList(ingredientList); + } catch (IllegalArgumentException e) { + throw new ParseException("No ingredient name has been provided for one or more ingredients!"); + } catch (IndexOutOfBoundsException e) { + throw new ParseException("No quantity has been provided for one or more ingredients!"); + } + } + + /** + * Parses {@code instructionString} into a {@link InstructionList}. Leading and + * trailing whitespaces will be trimmed. + * + * @throws ParseException if {@code instructionString} is blank, as specified by + * {@link String#isBlank()} + */ + public static InstructionList parseInstructions(String instructionString) throws ParseException { + requireNonNull(instructionString); + + if (instructionString.isBlank()) { + throw new ParseException("Recipes need to have instructions; please enter some instructions."); + } + + return new InstructionList(Stream.of(instructionString.trim().split(";")).map(String::trim) + .map(Instruction::new).collect(Collectors.toList())); + } + + /** + * Parses {@code filePathString} as a {@link Photograph}. + * + * @param filePathString The file path of a {@link Recipe}'s photo, as a + * {@link String} + * @return A Photograph from the file at {@code filePathString} + * @throws ParseException If {@code filePathString} is an invalid path, or the + * file does not exist. + */ + public static Photograph parsePhotoFilePath(String filePathString) throws ParseException { + requireNonNull(filePathString); + String trimmedPath = filePathString.trim().replaceAll("^['\"]*", "").replaceAll("['\"]*$", ""); + if (!FileUtil.isValidPathString(trimmedPath)) { + throw new ParseException(MESSAGE_INVALID_FILEPATH); + } + if (Photograph.IMAGE_UTIL.isPlaceHolderImage(filePathString)) { + return Photograph.PLACEHOLDER_PHOTOGRAPH; + } + try { + return new Photograph(trimmedPath); + } catch (IOException e) { + throw new ParseException(MESSAGE_INVALID_FILEPATH); + } + } + + /** + * Parses a {@code String calorie} into a {@code Calorie}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code calorie} is invalid. + */ + public static Calorie parseCalorie(String calorie) throws ParseException { + requireNonNull(calorie); + String trimmedCalorie = calorie.trim(); + if (!Calorie.isValidCalorieAmount(trimmedCalorie)) { + throw new ParseException(Calorie.MESSAGE_CONSTRAINTS); + } + return new Calorie(trimmedCalorie); + } + + /** + * Parses a {@code String servingString} into a {@code Serving}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code servingString} is invalid. + */ + public static Serving parseServing(String servingString) throws ParseException { + requireNonNull(servingString); + int serving; + try { + serving = Integer.parseInt(servingString.trim()); + } catch (NumberFormatException e) { + throw new ParseException("Input provided for serving is not a valid integer!"); + } + if (!Serving.isValidServing(serving)) { + throw new ParseException(Serving.MESSAGE_CONSTRAINTS); + } + return new Serving(serving); + } + + /** + * Parses a {@code String ratingString} into a {@code Rating}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code ratingString} is invalid. + */ + public static Rating parseRating(String ratingString) throws ParseException { + requireNonNull(ratingString); + int rating; + try { + rating = Integer.parseInt(ratingString.trim()); + } catch (NumberFormatException e) { + throw new ParseException("Input provided for rating is not a valid integer!"); + } + if (!Rating.isValidRating(rating)) { + throw new ParseException(Rating.MESSAGE_CONSTRAINTS); + } + return new Rating(rating); + } + + /** + * Parses a {@code String difficultyString} into a {@code Difficulty}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code difficultyString} is invalid. + */ + public static Difficulty parseDifficulty(String difficultyString) throws ParseException { + requireNonNull(difficultyString); + int difficulty; + try { + difficulty = Integer.parseInt(difficultyString.trim()); + } catch (NumberFormatException e) { + throw new ParseException("Input provided for difficulty is not a valid integer!"); + } + if (!Difficulty.isValidDifficulty(difficulty)) { + throw new ParseException(Difficulty.MESSAGE_CONSTRAINTS); + } + return new Difficulty(difficulty); + } + + /** + * Parses a {@code String tag} into a {@code Tag}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code tag} is invalid. + */ + public static Tag parseTag(String tag) throws ParseException { + requireNonNull(tag); + String trimmedTag = tag.trim(); + if (!Tag.isValidTagName(trimmedTag)) { + throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + } + return new Tag(trimmedTag); + } + + + /** + * Parses a {@code String timeString} into a {@code Time} + * + * @param timeString the string to be parsed + * @return the Time of the recipe + * @throws ParseException if the given {@timeString} is invalid. + */ + public static Time parseTime(String timeString) throws ParseException { + int hour; + int min = 0; + int sec = 0; + requireNonNull(timeString); + String trimmedTime = timeString.trim(); + String[] timeArray = trimmedTime.split(":"); + try { + hour = Integer.parseInt(timeArray[0]); + if (timeArray.length > 1) { + min = Integer.parseInt(timeArray[1]); + if (timeArray.length > 2) { + sec = Integer.parseInt(timeArray[2]); + } + } + } catch (NumberFormatException e) { + throw new ParseException("Input provided for time does not match the required pattern.\n" + + "Please enter the time in the following format: hh:MM:ss (minutes and seconds are optional, and will be" + + " set to 0 if no input is provided for them."); + } + if (!Time.isValidHour(hour)) { + throw new ParseException(Time.MESSAGE_CONSTRAINTS_HOUR); + } + + if (!Time.isValidMin(min)) { + throw new ParseException(Time.MESSAGE_CONSTRAINTS_MIN); + } + + if (!Time.isValidSec(sec)) { + throw new ParseException(Time.MESSAGE_CONSTRAINTS_SEC); + } + + return new Time(hour, min, sec); + + } + + /** + * Parses a {@code String command} into a {@Code string} + * @param command the command to be parsed. + * @return a trimmed string of the command word. + */ + public static String parseHelp (String command) { + requireNonNull(command); + String trimmedCommand = command.trim(); + return trimmedCommand; + } + + /** + * Parses {@code Optional tags} into a {@code Set}. + */ + public static Set parseTags(Optional tags) throws ParseException { + final Set tagSet = new HashSet<>(); + if (tags.isPresent() && !tags.get().equals("")) { + String tagsString = tags.get(); + List tagList = Stream.of(tagsString.split(",")).map(String::trim).collect( + Collectors.toList()); + for (String tagName : tagList) { + tagSet.add(parseTag(tagName)); + } + } + + return tagSet; + } +} diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/cookbuddy/logic/parser/Prefix.java similarity index 95% rename from src/main/java/seedu/address/logic/parser/Prefix.java rename to src/main/java/cookbuddy/logic/parser/Prefix.java index c859d5fa5db..a63be03b5b2 100644 --- a/src/main/java/seedu/address/logic/parser/Prefix.java +++ b/src/main/java/cookbuddy/logic/parser/Prefix.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package cookbuddy.logic.parser; /** * A prefix that marks the beginning of an argument in an arguments string. diff --git a/src/main/java/cookbuddy/logic/parser/RecipeBookParser.java b/src/main/java/cookbuddy/logic/parser/RecipeBookParser.java new file mode 100644 index 00000000000..5df86a84b11 --- /dev/null +++ b/src/main/java/cookbuddy/logic/parser/RecipeBookParser.java @@ -0,0 +1,125 @@ +package cookbuddy.logic.parser; + +import static cookbuddy.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static cookbuddy.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import cookbuddy.logic.commands.Command; +import cookbuddy.logic.commands.CountCommand; +import cookbuddy.logic.commands.DeleteCommand; +import cookbuddy.logic.commands.DoneCommand; +import cookbuddy.logic.commands.DuplicateCommand; +import cookbuddy.logic.commands.ExitCommand; +import cookbuddy.logic.commands.FavCommand; +import cookbuddy.logic.commands.FindCommand; +import cookbuddy.logic.commands.HelpCommand; +import cookbuddy.logic.commands.ListCommand; +import cookbuddy.logic.commands.ModifyCommand; +import cookbuddy.logic.commands.NewCommand; +import cookbuddy.logic.commands.RandomCommand; +import cookbuddy.logic.commands.ResetCommand; +import cookbuddy.logic.commands.TimeCommand; +import cookbuddy.logic.commands.UnFavCommand; +import cookbuddy.logic.commands.UndoCommand; +import cookbuddy.logic.commands.ViewCommand; +import cookbuddy.logic.parser.exceptions.ParseException; + +/** + * Parses user input. + */ +public class RecipeBookParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + switch (commandWord) { + + case NewCommand.COMMAND_WORD: + return new NewCommandParser().parse(arguments); + + case ModifyCommand.COMMAND_WORD: + return new ModifyCommandParser().parse(arguments); + + case DeleteCommand.COMMAND_WORD: + return new DeleteCommandParser().parse(arguments); + + case DoneCommand.COMMAND_WORD: + return new DoneCommandParser().parse(arguments); + + case DuplicateCommand.COMMAND_WORD: + return new DuplicateCommandParser().parse(arguments); + + case UndoCommand.COMMAND_WORD: + return new UndoCommandParser().parse(arguments); + + case FavCommand.COMMAND_WORD: + return new FavCommandParser().parse(arguments); + + case UnFavCommand.COMMAND_WORD: + return new UnFavCommandParser().parse(arguments); + + case ResetCommand.COMMAND_WORD: + if (!arguments.equals("")) { + throw new ParseException(("The reset command does not take in any arguments!")); + } + return new ResetCommand(); + + case ViewCommand.COMMAND_WORD: + return new ViewCommandParser().parse(arguments); + + case FindCommand.COMMAND_WORD: + return new FindCommandParser().parse(arguments); + + case ListCommand.COMMAND_WORD: + return new ListCommand(); + + case RandomCommand.COMMAND_WORD: + return new RandomCommand(); + + case TimeCommand.COMMAND_WORD: + return new TimeCommandParser().parse(arguments); + + case CountCommand.COMMAND_WORD: + if (!arguments.equals("")) { + throw new ParseException("The count command does not take in any arguments!"); + } + return new CountCommand(); + + case ExitCommand.COMMAND_WORD: + if (!arguments.equals("")) { + throw new ParseException("The exit command does not take in any arguments!"); + } + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + if (arguments.equals("")) { + return new HelpCommand(""); + } else { + return new HelpCommandParser().parse(arguments); + } + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } + +} diff --git a/src/main/java/cookbuddy/logic/parser/TimeCommandParser.java b/src/main/java/cookbuddy/logic/parser/TimeCommandParser.java new file mode 100644 index 00000000000..5b9239f3702 --- /dev/null +++ b/src/main/java/cookbuddy/logic/parser/TimeCommandParser.java @@ -0,0 +1,32 @@ +package cookbuddy.logic.parser; + +import static cookbuddy.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.TimeCommand; +import cookbuddy.logic.parser.exceptions.ParseException; +import cookbuddy.model.recipe.attribute.Time; + +/** + * Parses input arguments and creates a new FavCommand object + */ +public class TimeCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FavCommand + * and returns a FavCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public TimeCommand parse(String args) throws ParseException { + try { + String[] splitArgs = args.substring(1).split(" "); + Index index = ParserUtil.parseIndex(splitArgs[0]); + Time time = ParserUtil.parseTime(splitArgs[1]); + return new TimeCommand(index, time); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getLocalizedMessage(), TimeCommand.MESSAGE_USAGE)); + } + } + +} diff --git a/src/main/java/cookbuddy/logic/parser/UnFavCommandParser.java b/src/main/java/cookbuddy/logic/parser/UnFavCommandParser.java new file mode 100644 index 00000000000..66cc5a1a386 --- /dev/null +++ b/src/main/java/cookbuddy/logic/parser/UnFavCommandParser.java @@ -0,0 +1,30 @@ +package cookbuddy.logic.parser; + +import static cookbuddy.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.UnFavCommand; +import cookbuddy.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new UnFavCommand object + */ +public class UnFavCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the UnFavCommand + * and returns a UnFavCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public UnFavCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new UnFavCommand(index); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getLocalizedMessage()) + + "\nFor a command summary, type \"help unfav\""); + } + } + +} diff --git a/src/main/java/cookbuddy/logic/parser/UndoCommandParser.java b/src/main/java/cookbuddy/logic/parser/UndoCommandParser.java new file mode 100644 index 00000000000..90a007eda96 --- /dev/null +++ b/src/main/java/cookbuddy/logic/parser/UndoCommandParser.java @@ -0,0 +1,31 @@ +package cookbuddy.logic.parser; + +import static cookbuddy.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.UndoCommand; +import cookbuddy.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new UndoCommand object + */ +public class UndoCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the UndoCommand + * and returns a UndoCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public UndoCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new UndoCommand(index); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getLocalizedMessage()) + + "\nFor a command summary, type \"help undo\""); + } + } + +} diff --git a/src/main/java/cookbuddy/logic/parser/ViewCommandParser.java b/src/main/java/cookbuddy/logic/parser/ViewCommandParser.java new file mode 100644 index 00000000000..d73f838c995 --- /dev/null +++ b/src/main/java/cookbuddy/logic/parser/ViewCommandParser.java @@ -0,0 +1,33 @@ +package cookbuddy.logic.parser; + +import static cookbuddy.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import cookbuddy.commons.core.index.Index; +import cookbuddy.logic.commands.ViewCommand; +import cookbuddy.logic.parser.exceptions.ParseException; + + + + +/** + * Parses input arguments and creates a new UnFavCommand object + */ +public class ViewCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the UnFavCommand + * and returns a UnFavCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ViewCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new ViewCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getLocalizedMessage()) + "\nFor a command " + + "summary, type \"help view\""); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/cookbuddy/logic/parser/exceptions/ParseException.java similarity index 69% rename from src/main/java/seedu/address/logic/parser/exceptions/ParseException.java rename to src/main/java/cookbuddy/logic/parser/exceptions/ParseException.java index 158a1a54c1c..1e4de118bf5 100644 --- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java +++ b/src/main/java/cookbuddy/logic/parser/exceptions/ParseException.java @@ -1,10 +1,11 @@ -package seedu.address.logic.parser.exceptions; +package cookbuddy.logic.parser.exceptions; -import seedu.address.commons.exceptions.IllegalValueException; +import cookbuddy.commons.exceptions.IllegalValueException; /** * Represents a parse error encountered by a parser. */ +@SuppressWarnings("serial") public class ParseException extends IllegalValueException { public ParseException(String message) { diff --git a/src/main/java/cookbuddy/model/Model.java b/src/main/java/cookbuddy/model/Model.java new file mode 100644 index 00000000000..cc98f3f493d --- /dev/null +++ b/src/main/java/cookbuddy/model/Model.java @@ -0,0 +1,127 @@ +package cookbuddy.model; + +import java.nio.file.Path; +import java.util.function.Predicate; + +import cookbuddy.commons.core.GuiSettings; +import cookbuddy.model.recipe.Recipe; +import cookbuddy.model.recipe.attribute.Time; +import javafx.collections.ObservableList; + +/** + * The API of the Model component. + */ +public interface Model { + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_RECIPES = unused -> true; + /** {@code Predicate} that always evaluates to false */ + Predicate PREDICATE_SHOW_NO_RECIPES = unused -> false; + + /** + * Replaces user prefs data with the data in {@code userPrefs}. + */ + void setUserPrefs(ReadOnlyUserPrefs userPrefs); + + /** + * Returns the user prefs. + */ + ReadOnlyUserPrefs getUserPrefs(); + + /** + * Returns the user prefs' GUI settings. + */ + GuiSettings getGuiSettings(); + + /** + * Sets the user prefs' GUI settings. + */ + void setGuiSettings(GuiSettings guiSettings); + + /** + * Returns the user prefs' recipe book file path. + */ + Path getRecipeBookFilePath(); + + /** + * Sets the user prefs' recipe book file path. + */ + void setRecipeBookFilePath(Path recipeBookFilePath); + + /** + * Replaces recipe book data with the data in {@code recipeBook}. + */ + void setRecipeBook(ReadOnlyRecipeBook recipeBook); + + /** Returns the RecipeBook */ + ReadOnlyRecipeBook getRecipeBook(); + + /** + * Returns true if a recipe with the same identity as {@code recipe} exists in the recipe book. + */ + boolean hasRecipe(Recipe recipe); + + /** + * Returns the total number of recipes in the recipe book. + */ + long count(); + + + /** + * Marks a recipe as attempted/done + * @param recipe the recipe to be marked. + */ + void attemptRecipe(Recipe recipe); + + /** + * Un-Makrs a recipe as attempted/done + * @param recipe the recipe to be un-marked. + */ + void unAttemptRecipe(Recipe recipe); + + /** + * + * Favourites the recipe + */ + void favRecipe(Recipe recipe); + + /** + * Un-Favourites the recipe + * + */ + void unFavRecipe(Recipe recipe); + + /** + * Sets a time to the recipe + * @param recipe the recipe to be set + * @param time the prep time of the recipe. + */ + void setTime(Recipe recipe, Time time); + + /** + * Deletes the given recipe. + * The recipe must exist in the recipe book. + */ + void deleteRecipe(Recipe target); + + /** + * Adds the given recipe. + * {@code recipe} must not already exist in the recipe book. + */ + void addRecipe(Recipe recipe); + + /** + * Replaces the given recipe {@code target} with {@code editedRecipe}. + * {@code target} must exist in the recipe book. + * The recipe identity of {@code editedRecipe} must not be the same as another existing recipe in the recipe book. + */ + void setRecipe(Recipe target, Recipe editedRecipe); + + /** Returns an unmodifiable view of the filtered recipe list */ + ObservableList getFilteredRecipeList(); + + /** + * Updates the filter of the filtered recipe list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredRecipeList(Predicate predicate); +} diff --git a/src/main/java/cookbuddy/model/ModelManager.java b/src/main/java/cookbuddy/model/ModelManager.java new file mode 100644 index 00000000000..bd31914b868 --- /dev/null +++ b/src/main/java/cookbuddy/model/ModelManager.java @@ -0,0 +1,191 @@ +package cookbuddy.model; + +import static cookbuddy.commons.util.CollectionUtil.requireAllNonNull; +import static java.util.Objects.requireNonNull; + +import java.nio.file.Path; +import java.util.function.Predicate; +import java.util.logging.Logger; + +import cookbuddy.commons.core.GuiSettings; +import cookbuddy.commons.core.LogsCenter; +import cookbuddy.model.recipe.Recipe; +import cookbuddy.model.recipe.attribute.Time; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; + +/** + * Represents the in-memory model of the recipe book data. + */ +public class ModelManager implements Model { + private static final Logger logger = LogsCenter.getLogger(ModelManager.class); + + private final RecipeBook recipeBook; + private final UserPrefs userPrefs; + private final FilteredList filteredRecipes; + + /** + * Initializes a ModelManager with the given recipeBook and userPrefs. + */ + public ModelManager(ReadOnlyRecipeBook recipeBook, ReadOnlyUserPrefs userPrefs) { + super(); + requireAllNonNull(recipeBook, userPrefs); + + logger.fine("Initializing with recipe book: " + recipeBook + " and user prefs " + userPrefs); + + this.recipeBook = new RecipeBook(recipeBook); + this.userPrefs = new UserPrefs(userPrefs); + filteredRecipes = new FilteredList<>(this.recipeBook.getRecipeList()); + } + + public ModelManager() { + this(new RecipeBook(), new UserPrefs()); + } + + //=========== UserPrefs ================================================================================== + + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + requireNonNull(userPrefs); + this.userPrefs.resetData(userPrefs); + } + + @Override + public ReadOnlyUserPrefs getUserPrefs() { + return userPrefs; + } + + @Override + public GuiSettings getGuiSettings() { + return userPrefs.getGuiSettings(); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + requireNonNull(guiSettings); + userPrefs.setGuiSettings(guiSettings); + } + + @Override + public Path getRecipeBookFilePath() { + return userPrefs.getDataFilePath(); + } + + @Override + public void setRecipeBookFilePath(Path recipeBookFilePath) { + requireNonNull(recipeBookFilePath); + userPrefs.setDataFilePath(recipeBookFilePath); + } + + //=========== RecipeBook ================================================================================ + + @Override + public void setRecipeBook(ReadOnlyRecipeBook recipeBook) { + this.recipeBook.resetData(recipeBook); + } + + @Override + public ReadOnlyRecipeBook getRecipeBook() { + return recipeBook; + } + + @Override + public boolean hasRecipe(Recipe recipe) { + requireNonNull(recipe); + return recipeBook.hasRecipe(recipe); + } + + @Override + public void attemptRecipe(Recipe recipe) { + recipeBook.attempt(recipe); + updateFilteredRecipeList(PREDICATE_SHOW_NO_RECIPES); + updateFilteredRecipeList(PREDICATE_SHOW_ALL_RECIPES); + } + + @Override + public void unAttemptRecipe(Recipe recipe) { + recipeBook.unAttempt(recipe); + updateFilteredRecipeList(PREDICATE_SHOW_NO_RECIPES); + updateFilteredRecipeList(PREDICATE_SHOW_ALL_RECIPES); + } + + @Override + public void favRecipe(Recipe recipe) { + recipeBook.fav(recipe); + updateFilteredRecipeList(PREDICATE_SHOW_NO_RECIPES); + updateFilteredRecipeList(PREDICATE_SHOW_ALL_RECIPES); + } + + @Override + public void unFavRecipe(Recipe recipe) { + recipeBook.unFav(recipe); + updateFilteredRecipeList(PREDICATE_SHOW_NO_RECIPES); + updateFilteredRecipeList(PREDICATE_SHOW_ALL_RECIPES); + } + + @Override + public long count() { + return recipeBook.count(); + } + + @Override + public void setTime(Recipe recipe, Time time) { + recipe.setTime(time); + updateFilteredRecipeList(PREDICATE_SHOW_NO_RECIPES); + updateFilteredRecipeList(PREDICATE_SHOW_ALL_RECIPES); + } + + @Override + public void deleteRecipe(Recipe target) { + recipeBook.removeRecipe(target); + } + + @Override + public void addRecipe(Recipe recipe) { + recipeBook.addRecipe(recipe); + updateFilteredRecipeList(PREDICATE_SHOW_ALL_RECIPES); + } + + @Override + public void setRecipe(Recipe target, Recipe editedRecipe) { + requireAllNonNull(target, editedRecipe); + recipeBook.setRecipe(target, editedRecipe); + } + + //=========== Filtered Recipe List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Recipe} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredRecipeList() { + return filteredRecipes; + } + + @Override + public void updateFilteredRecipeList(Predicate predicate) { + requireNonNull(predicate); + filteredRecipes.setPredicate(predicate); + } + + @Override + public boolean equals(Object obj) { + // short circuit if same object + if (obj == this) { + return true; + } + + // instanceof handles nulls + if (!(obj instanceof ModelManager)) { + return false; + } + + // state check + ModelManager other = (ModelManager) obj; + return recipeBook.equals(other.recipeBook) + && userPrefs.equals(other.userPrefs) + && filteredRecipes.equals(other.filteredRecipes); + } + +} diff --git a/src/main/java/cookbuddy/model/ReadOnlyRecipeBook.java b/src/main/java/cookbuddy/model/ReadOnlyRecipeBook.java new file mode 100644 index 00000000000..2af13cd0862 --- /dev/null +++ b/src/main/java/cookbuddy/model/ReadOnlyRecipeBook.java @@ -0,0 +1,17 @@ +package cookbuddy.model; + +import cookbuddy.model.recipe.Recipe; +import javafx.collections.ObservableList; + +/** + * Unmodifiable view of an recipe book + */ +public interface ReadOnlyRecipeBook { + + /** + * Returns an unmodifiable view of the recipe list. + * This list will not contain any duplicate recipe. + */ + ObservableList getRecipeList(); + +} diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/cookbuddy/model/ReadOnlyUserPrefs.java similarity index 54% rename from src/main/java/seedu/address/model/ReadOnlyUserPrefs.java rename to src/main/java/cookbuddy/model/ReadOnlyUserPrefs.java index befd58a4c73..24455ea0ae7 100644 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ b/src/main/java/cookbuddy/model/ReadOnlyUserPrefs.java @@ -1,8 +1,8 @@ -package seedu.address.model; +package cookbuddy.model; import java.nio.file.Path; -import seedu.address.commons.core.GuiSettings; +import cookbuddy.commons.core.GuiSettings; /** * Unmodifiable view of user prefs. @@ -11,6 +11,8 @@ public interface ReadOnlyUserPrefs { GuiSettings getGuiSettings(); - Path getAddressBookFilePath(); + Path getDataFilePath(); + + Path getImagesPath(); } diff --git a/src/main/java/cookbuddy/model/RecipeBook.java b/src/main/java/cookbuddy/model/RecipeBook.java new file mode 100644 index 00000000000..bd58fa128ed --- /dev/null +++ b/src/main/java/cookbuddy/model/RecipeBook.java @@ -0,0 +1,171 @@ +package cookbuddy.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import cookbuddy.model.recipe.Recipe; +import cookbuddy.model.recipe.UniqueRecipeList; +import javafx.collections.ObservableList; + +/** + * Wraps all data at the recipe-book level + * Duplicates are not allowed (by .isSameRecipe comparison) + */ +public class RecipeBook implements ReadOnlyRecipeBook { + + private final UniqueRecipeList recipes; + + /** + * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication + * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html + * + * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication + * among constructors. + */ + { + recipes = new UniqueRecipeList(); + } + + public RecipeBook() { + } + + /** + * Creates an RecipeBook using the Recipes in the {@code toBeCopied} + */ + public RecipeBook(ReadOnlyRecipeBook toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// list overwrite operations + + /** + * Replaces the contents of the recipe list with {@code recipes}. + * {@code recipes} must not contain duplicate recipes. + */ + public void setRecipes(List recipes) { + this.recipes.setRecipes(recipes); + } + + /** + * Resets the existing data of this {@code RecipeBook} with {@code newData}. + */ + public void resetData(ReadOnlyRecipeBook newData) { + requireNonNull(newData); + + setRecipes(newData.getRecipeList()); + } + + //// recipe-level operations + + /** + * Returns true if a recipe with the same identity as {@code recipe} exists in the recipe book. + */ + public boolean hasRecipe(Recipe recipe) { + requireNonNull(recipe); + return recipes.contains(recipe); + } + + /** + * Returns the total number of recipes in the recipe book. + */ + public long count() { + return recipes.count(); + } + + /** + * favourites a recipe from the recipe book. + * @param recipe the recipe to be favourited. + */ + public void fav(Recipe recipe) { + requireNonNull(recipe); + recipe.favRecipe(); + } + + /** + * Un-favourites a recipe from the recipe book. + * @param recipe the recipe to be un-favourited. + */ + public void unFav(Recipe recipe) { + requireNonNull(recipe); + recipe.unFavRecipe(); + } + + /** + * Attempts the recipe, marking it as done. + * @param recipe the recipe to be marked as done. + */ + public void attempt(Recipe recipe) { + requireNonNull(recipe); + recipe.attemptRecipe(); + } + + /** + * Un-Marks the recipe as attempted, marking it as not done. + * @param recipe the recipe to be un-marked. + */ + public void unAttempt(Recipe recipe) { + requireNonNull(recipe); + recipe.unAttemptRecipe(); + } + + + /** + * Adds a recipe to the recipe book. + * The recipe must not already exist in the recipe book. + */ + public void addRecipe(Recipe recipe) { + recipes.add(recipe); + } + + /** + * Replaces the given recipe {@code target} in the list with {@code editedRecipe}. + * {@code target} must exist in the recipe book. + * The recipe identity of {@code editedRecipe} must not be the same as another existing recipe in the recipe book. + */ + public void setRecipe(Recipe target, Recipe editedRecipe) { + requireNonNull(editedRecipe); + if (target.getFavStatus().toString().equals("\u2665")) { + editedRecipe.favRecipe(); + } + + if (target.getDoneStatus().toString().equals("Yes")) { + editedRecipe.attemptRecipe(); + } + + recipes.setRecipe(target, editedRecipe); + } + + /** + * Removes {@code key} from this {@code RecipeBook}. + * {@code key} must exist in the recipe book. + */ + public void removeRecipe(Recipe key) { + recipes.remove(key); + } + + //// util methods + + @Override + public String toString() { + return recipes.asUnmodifiableObservableList().size() + " recipes"; + } + + @Override + public ObservableList getRecipeList() { + return recipes.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RecipeBook // instanceof handles nulls + && recipes.equals(((RecipeBook) other).recipes)); + } + + @Override + public int hashCode() { + return recipes.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/cookbuddy/model/UserPrefs.java similarity index 58% rename from src/main/java/seedu/address/model/UserPrefs.java rename to src/main/java/cookbuddy/model/UserPrefs.java index 25a5fd6eab9..124ba443b5f 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/cookbuddy/model/UserPrefs.java @@ -1,4 +1,4 @@ -package seedu.address.model; +package cookbuddy.model; import static java.util.Objects.requireNonNull; @@ -6,7 +6,8 @@ import java.nio.file.Paths; import java.util.Objects; -import seedu.address.commons.core.GuiSettings; +import cookbuddy.commons.core.GuiSettings; +import cookbuddy.commons.util.PhotographUtil; /** * Represents User's preferences. @@ -14,7 +15,8 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path dataFilePath = Paths.get("data" , "recipebook.json"); + private Path recipeImagePath = PhotographUtil.imageUtil().defaultStoragePath; /** * Creates a {@code UserPrefs} with default values. @@ -35,9 +37,11 @@ public UserPrefs(ReadOnlyUserPrefs userPrefs) { public void resetData(ReadOnlyUserPrefs newUserPrefs) { requireNonNull(newUserPrefs); setGuiSettings(newUserPrefs.getGuiSettings()); - setAddressBookFilePath(newUserPrefs.getAddressBookFilePath()); + setDataFilePath(newUserPrefs.getDataFilePath()); + setImagesPath(newUserPrefs.getImagesPath()); } + public GuiSettings getGuiSettings() { return guiSettings; } @@ -47,15 +51,26 @@ public void setGuiSettings(GuiSettings guiSettings) { this.guiSettings = guiSettings; } - public Path getAddressBookFilePath() { - return addressBookFilePath; + public Path getDataFilePath() { + return dataFilePath; + } + + public void setDataFilePath(Path dataFilePath) { + requireNonNull(dataFilePath); + this.dataFilePath = dataFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - this.addressBookFilePath = addressBookFilePath; + @Override + public Path getImagesPath() { + return recipeImagePath; } + public void setImagesPath(Path recipeImagePath) { + requireNonNull(recipeImagePath); + this.recipeImagePath = recipeImagePath; + } + + @Override public boolean equals(Object other) { if (other == this) { @@ -68,19 +83,21 @@ public boolean equals(Object other) { UserPrefs o = (UserPrefs) other; return guiSettings.equals(o.guiSettings) - && addressBookFilePath.equals(o.addressBookFilePath); + && dataFilePath.equals(o.dataFilePath) + && recipeImagePath.equals(o.recipeImagePath); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); + return Objects.hash(guiSettings, dataFilePath, recipeImagePath); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("\nLocal data file location : " + dataFilePath); + sb.append("\nRecipes image location : " + recipeImagePath); return sb.toString(); } diff --git a/src/main/java/cookbuddy/model/recipe/ContainsKeywordsPredicate.java b/src/main/java/cookbuddy/model/recipe/ContainsKeywordsPredicate.java new file mode 100644 index 00000000000..35fe7bc8f67 --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/ContainsKeywordsPredicate.java @@ -0,0 +1,10 @@ +package cookbuddy.model.recipe; + +import java.util.function.Predicate; + +/** + * Predicate to test for. + */ +public interface ContainsKeywordsPredicate extends Predicate { + boolean test(Recipe recipe); +} diff --git a/src/main/java/cookbuddy/model/recipe/IngredientContainsKeywordsPredicate.java b/src/main/java/cookbuddy/model/recipe/IngredientContainsKeywordsPredicate.java new file mode 100644 index 00000000000..5ab73ddf5e5 --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/IngredientContainsKeywordsPredicate.java @@ -0,0 +1,35 @@ +package cookbuddy.model.recipe; + +import java.util.List; +import java.util.stream.Collectors; + +import cookbuddy.commons.util.StringUtil; + +/** + * Tests that a {@code Recipe}'s {@code IngredientList} matches any of the keywords given. + */ +public class IngredientContainsKeywordsPredicate implements ContainsKeywordsPredicate { + private final List keywords; + + public IngredientContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Recipe recipe) { + List ingredients = recipe.getIngredients().ingredientData.stream() + .map(x -> x.name) + .collect(Collectors.toList()); + + return keywords.stream().anyMatch(keyword -> ingredients.stream() + .anyMatch(ingredient -> StringUtil.containsWordIgnoreCase(ingredient, keyword))); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof IngredientContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((IngredientContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/cookbuddy/model/recipe/InstructionContainsKeywordsPredicate.java b/src/main/java/cookbuddy/model/recipe/InstructionContainsKeywordsPredicate.java new file mode 100644 index 00000000000..ba51ca27da1 --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/InstructionContainsKeywordsPredicate.java @@ -0,0 +1,35 @@ +package cookbuddy.model.recipe; + +import java.util.List; +import java.util.stream.Collectors; + +import cookbuddy.commons.util.StringUtil; + +/** + * Tests that a {@code Recipe}'s {@code IngredientList} matches any of the keywords given. + */ +public class InstructionContainsKeywordsPredicate implements ContainsKeywordsPredicate { + private final List keywords; + + public InstructionContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Recipe recipe) { + List instructions = recipe.getInstructions().instructionData.stream() + .map(x -> x.instructionString) + .collect(Collectors.toList()); + + return keywords.stream().anyMatch(keyword -> instructions.stream() + .anyMatch(instruction -> StringUtil.containsWordIgnoreCase(instruction, keyword))); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof InstructionContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((InstructionContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/cookbuddy/model/recipe/NameContainsKeywordsPredicate.java similarity index 65% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/cookbuddy/model/recipe/NameContainsKeywordsPredicate.java index c9b5868427c..0d00977b24f 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/cookbuddy/model/recipe/NameContainsKeywordsPredicate.java @@ -1,14 +1,13 @@ -package seedu.address.model.person; +package cookbuddy.model.recipe; import java.util.List; -import java.util.function.Predicate; -import seedu.address.commons.util.StringUtil; +import cookbuddy.commons.util.StringUtil; /** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + * Tests that a {@code Recipe}'s {@code Name} matches any of the keywords given. */ -public class NameContainsKeywordsPredicate implements Predicate { +public class NameContainsKeywordsPredicate implements ContainsKeywordsPredicate { private final List keywords; public NameContainsKeywordsPredicate(List keywords) { @@ -16,9 +15,9 @@ public NameContainsKeywordsPredicate(List keywords) { } @Override - public boolean test(Person person) { + public boolean test(Recipe recipe) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(recipe.getName().toString(), keyword)); } @Override diff --git a/src/main/java/cookbuddy/model/recipe/Recipe.java b/src/main/java/cookbuddy/model/recipe/Recipe.java new file mode 100644 index 00000000000..abc920c84be --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/Recipe.java @@ -0,0 +1,187 @@ +package cookbuddy.model.recipe; + +import static cookbuddy.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import cookbuddy.model.recipe.attribute.Calorie; +import cookbuddy.model.recipe.attribute.Difficulty; +import cookbuddy.model.recipe.attribute.Done; +import cookbuddy.model.recipe.attribute.Fav; +import cookbuddy.model.recipe.attribute.IngredientList; +import cookbuddy.model.recipe.attribute.InstructionList; +import cookbuddy.model.recipe.attribute.Name; +import cookbuddy.model.recipe.attribute.Photograph; +import cookbuddy.model.recipe.attribute.Rating; +import cookbuddy.model.recipe.attribute.Serving; +import cookbuddy.model.recipe.attribute.Tag; +import cookbuddy.model.recipe.attribute.Time; + +/** + * Represents a Recipe in the recipe book. Guarantees: details are present and + * not null, field values are validated, immutable. + */ +public class Recipe { + + // Identity fields + private final Name name; + private final IngredientList ingredients; + private final InstructionList instructions; + private final Photograph photograph; + private final Calorie calorie; + private final Serving serving; + private final Rating rating; + private final Difficulty difficulty; + private final Fav favStatus = new Fav(false); + private final Done doneStatus = new Done(false); + private final Time prepTime = new Time(0, 0, 0); + // Data fields + private final Set tags = new HashSet<>(); + + /** + * Every field must be present and not null. + */ + public Recipe(Name name, IngredientList ingredients, InstructionList instructions, Photograph photograph, + Calorie calorie, Serving serving, Rating rating, Difficulty difficulty, Set tags) { + requireAllNonNull(name, ingredients, instructions); + this.name = name; + this.ingredients = ingredients; + this.instructions = instructions; + this.photograph = photograph; + this.calorie = calorie; + this.serving = serving; + this.rating = rating; + this.difficulty = difficulty; + this.tags.addAll(tags); + } + + public Name getName() { + return name; + } + + public IngredientList getIngredients() { + return ingredients; + } + + public InstructionList getInstructions() { + return instructions; + } + + public Photograph getPhotograph() { + return photograph; + } + + public Calorie getCalorie() { + return calorie; + } + + public Rating getRating() { + return rating; + } + + public Serving getServing() { + return serving; + } + + public Difficulty getDifficulty() { + return difficulty; + } + + public Fav getFavStatus() { + return favStatus; + } + + public Done getDoneStatus() { + return doneStatus; + } + + public Time getPrepTime() { + return prepTime; + } + + /** + * Returns an immutable tag set, which throws + * {@code UnsupportedOperationException} if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + + /** + * Returns true if both recipes of the same name have the same ingredients and instructions. This defines a weaker + * notion of equality between two recipes. + */ + public boolean isSameRecipe(Recipe otherRecipe) { + if (otherRecipe == this) { + return true; + } + + return otherRecipe != null && otherRecipe.getName().equals(getName()) + && otherRecipe.getIngredients().equals(getIngredients()) + && otherRecipe.getInstructions().equals(getInstructions()) + && otherRecipe.getPhotograph().equals(getPhotograph()); + } + + public void favRecipe() { + favStatus.fav(); + } + + public void unFavRecipe() { + favStatus.unFav(); + } + + public void attemptRecipe() { + doneStatus.attempt(); + } + + public void unAttemptRecipe() { + doneStatus.unAttempt(); + } + + public void setTime(Time time) { + prepTime.setTime(time); + } + + /** + * Returns true if both recipes have the same identity and data fields. This + * defines a stronger notion of equality between two recipes. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Recipe)) { + return false; + } + Recipe otherRecipe = (Recipe) other; + return otherRecipe.getName().equals(getName()) && otherRecipe.getIngredients().equals(getIngredients()) + && otherRecipe.getInstructions().equals(getInstructions()) && otherRecipe.getPhotograph() + .equals(getPhotograph()) + && otherRecipe.getCalorie().equals(getCalorie()) && otherRecipe.getRating().equals(getRating()) + && otherRecipe.getDifficulty().equals(getDifficulty()) + && otherRecipe.getTags().equals(getTags()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, ingredients, instructions, photograph, calorie, serving, rating, difficulty, tags); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()).append(" Ingredients: ").append(getIngredients()).append( + " Instructions: ").append(getInstructions()).append(" Path: ").append(getPhotograph()).append( + " Calories: ").append(getCalorie()).append(" Serving size: ").append(getServing().serving).append( + " Rating: ").append(getRating().rating).append(" Difficulty ").append(getDifficulty().difficulty).append( + " Tags" + ": "); + getTags().forEach(builder::append); + return builder.toString(); + } +} diff --git a/src/main/java/cookbuddy/model/recipe/UniqueRecipeList.java b/src/main/java/cookbuddy/model/recipe/UniqueRecipeList.java new file mode 100644 index 00000000000..470c757fad6 --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/UniqueRecipeList.java @@ -0,0 +1,148 @@ +package cookbuddy.model.recipe; + +import static cookbuddy.commons.util.CollectionUtil.requireAllNonNull; +import static java.util.Objects.requireNonNull; + +import java.util.Iterator; +import java.util.List; + +import cookbuddy.model.recipe.exceptions.DuplicateRecipeException; +import cookbuddy.model.recipe.exceptions.RecipeNotFoundException; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * A list of recipes that enforces uniqueness between its elements and does not allow nulls. + * A recipe is considered unique by comparing using {@code Recipe#isSameRecipe(Recipe)}. As such, adding and updating of + * recipes uses Recipe#isSameRecipe(Recipe) for equality so as to ensure that the recipe being added or updated is + * unique in terms of identity in the UniqueRecipeList. However, the removal of a recipe uses Recipe#equals(Object) so + * as to ensure that the recipe with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Recipe#isSameRecipe(Recipe) + */ +public class UniqueRecipeList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent recipe as the given argument. + */ + public boolean contains(Recipe toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameRecipe); + } + + /** + * Returns the total number of recipes in the list. + */ + public long count() { + return internalList.size(); + } + + /** + * Adds a recipe to the list. + * The recipe must not already exist in the list. + */ + public void add(Recipe toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateRecipeException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the recipe {@code target} in the list with {@code editedRecipe}. + * {@code target} must exist in the list. + * The recipe identity of {@code editedRecipe} must not be the same as another existing recipe in the list. + */ + public void setRecipe(Recipe target, Recipe editedRecipe) { + requireAllNonNull(target, editedRecipe); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new RecipeNotFoundException(); + } + + if (!target.isSameRecipe(editedRecipe) && contains(editedRecipe)) { + throw new DuplicateRecipeException(); + } + if (target.getFavStatus().toString().equals("\2665") == true) { + editedRecipe.favRecipe(); + } + + internalList.set(index, editedRecipe); + + } + + /** + * Removes the equivalent recipe from the list. + * The recipe must exist in the list. + */ + public void remove(Recipe toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new RecipeNotFoundException(); + } + } + + public void setRecipes(UniqueRecipeList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code recipes}. + * {@code recipes} must not contain duplicate recipes. + */ + public void setRecipes(List recipes) { + requireAllNonNull(recipes); + if (!recipesAreUnique(recipes)) { + throw new DuplicateRecipeException(); + } + + internalList.setAll(recipes); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueRecipeList // instanceof handles nulls + && internalList.equals(((UniqueRecipeList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code recipes} contains only unique recipes. + */ + private boolean recipesAreUnique(List recipes) { + for (int i = 0; i < recipes.size() - 1; i++) { + for (int j = i + 1; j < recipes.size(); j++) { + if (recipes.get(i).isSameRecipe(recipes.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/cookbuddy/model/recipe/attribute/Calorie.java b/src/main/java/cookbuddy/model/recipe/attribute/Calorie.java new file mode 100644 index 00000000000..e4f354301e3 --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/attribute/Calorie.java @@ -0,0 +1,54 @@ +package cookbuddy.model.recipe.attribute; + +import static cookbuddy.commons.util.AppUtil.checkArgument; +import static java.util.Objects.requireNonNull; + +/** + * Represents a Calorie in the recipe book. + * Guarantees: immutable; calorie is valid as declared in {@link #isValidCalorieAmount(String)} + */ +public class Calorie { + + public static final String MESSAGE_CONSTRAINTS = "Calorie amount should be a positive integer value."; + public static final String VALIDATION_REGEX = "\\d+"; + + public final String calorie; + + /** + * Constructs a {@code Calorie}. + * + * @param calorie A valid calorie amount. + */ + public Calorie(String calorie) { + requireNonNull(calorie); + checkArgument(isValidCalorieAmount(calorie), MESSAGE_CONSTRAINTS); + this.calorie = calorie; + } + + /** + * Returns true if a given string is a valid calorie amount. + */ + public static boolean isValidCalorieAmount(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Calorie // instanceof handles nulls + && calorie.equals(((Calorie) other).calorie)); // state check + } + + @Override + public int hashCode() { + return calorie.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return calorie; + } + +} diff --git a/src/main/java/cookbuddy/model/recipe/attribute/Difficulty.java b/src/main/java/cookbuddy/model/recipe/attribute/Difficulty.java new file mode 100644 index 00000000000..b262332d99c --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/attribute/Difficulty.java @@ -0,0 +1,53 @@ +package cookbuddy.model.recipe.attribute; + +import static cookbuddy.commons.util.AppUtil.checkArgument; +import static java.util.Objects.requireNonNull; + +/** + * Represents the Difficulty of the recipe in the recipe book. + * Guarantees: immutable; difficulty is valid as declared in {@link #isValidDifficulty(int)} + */ +public class Difficulty { + + public static final String MESSAGE_CONSTRAINTS = "Difficulty should be an integer >= 0 and <= 5"; + + public final int difficulty; + + /** + * Constructs a {@code Difficulty}. + * + * @param difficulty A valid difficulty. + */ + public Difficulty(int difficulty) { + requireNonNull(difficulty); + checkArgument(isValidDifficulty(difficulty), MESSAGE_CONSTRAINTS); + this.difficulty = difficulty; + } + + + /** + * Returns true if a given string is a valid difficulty. + */ + public static boolean isValidDifficulty(int test) { + return (test >= 0 && test <= 5); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Difficulty // instanceof handles nulls + && difficulty == (((Difficulty) other).difficulty)); // state check + } + + @Override + public int hashCode() { + return String.valueOf(difficulty).hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return String.valueOf(difficulty); + } +} diff --git a/src/main/java/cookbuddy/model/recipe/attribute/Done.java b/src/main/java/cookbuddy/model/recipe/attribute/Done.java new file mode 100644 index 00000000000..111d474773c --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/attribute/Done.java @@ -0,0 +1,61 @@ +package cookbuddy.model.recipe.attribute; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a Recipe's Done status in the recipe book. + * Guarantees: mutable. + */ +public class Done { + + public static final String MESSAGE_CONSTRAINTS = + "A recipe can only be done or not!"; + + + private boolean doneStatus; + + /** + * Constructs a {@code Done status}. + * + * @param status true or false. + */ + public Done(boolean status) { + requireNonNull(status); + this.doneStatus = status; + } + + public void attempt() { + this.doneStatus = true; + } + + public void unAttempt() { + this.doneStatus = false; + } + + public boolean getDoneStatus() { + return doneStatus; + } + + + @Override + public String toString() { + if (doneStatus == true) { + return "Yes"; + } else { + return "No"; + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Done // instanceof handles nulls + && doneStatus == (((Done) other).doneStatus)); // state check + } + + @Override + public int hashCode() { + return String.valueOf(doneStatus).hashCode(); + } + +} diff --git a/src/main/java/cookbuddy/model/recipe/attribute/Fav.java b/src/main/java/cookbuddy/model/recipe/attribute/Fav.java new file mode 100644 index 00000000000..fdd433873a2 --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/attribute/Fav.java @@ -0,0 +1,61 @@ +package cookbuddy.model.recipe.attribute; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a Recipe's favourite status in the recipe book. + * Guarantees: mutable. + */ +public class Fav { + + public static final String MESSAGE_CONSTRAINTS = + "Favourites can only be true or false!"; + + + private boolean favStatus; + + /** + * Constructs a {@code Fav status}. + * + * @param status true or false. + */ + public Fav(boolean status) { + requireNonNull(status); + this.favStatus = status; + } + + public void fav() { + this.favStatus = true; + } + + public void unFav() { + this.favStatus = false; + } + + public boolean getfavStatus() { + return favStatus; + } + + + @Override + public String toString() { + if (favStatus == true) { + return "\u2665"; + } else { + return "\u2661"; + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Fav // instanceof handles nulls + && favStatus == (((Fav) other).favStatus)); // state check + } + + @Override + public int hashCode() { + return String.valueOf(favStatus).hashCode(); + } + +} diff --git a/src/main/java/cookbuddy/model/recipe/attribute/Ingredient.java b/src/main/java/cookbuddy/model/recipe/attribute/Ingredient.java new file mode 100644 index 00000000000..8f5da516497 --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/attribute/Ingredient.java @@ -0,0 +1,80 @@ +package cookbuddy.model.recipe.attribute; + +import static cookbuddy.commons.util.AppUtil.checkArgument; +import static java.util.Objects.requireNonNull; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Represents a Recipe's ingredient in its {@code IngredientList}. Guarantees: + * immutable; is valid as declared in {@link #isValidName(String)} + */ +public class Ingredient { + + public static final String MESSAGE_CONSTRAINTS = "Each ingredient should be of the form: " + + "'ing1, ing1qty'.\n" + + "Each ingredient-quantity pair must be separated by ';'. Spaces are optional.\n" + + "Example: 'ing/bread, 2 slices; ham, 3 slices'"; + + public final String name; + private String quantity; + private Quantity quantity2; + + /** + * Construct an Ingredient from {@code ingredientString}; the parameter is + * required to be non-null, and must follow a pattern. + * + * @param ingredientString the {@link String} to be decoded + */ + public Ingredient(String ingredientString) { + requireNonNull(ingredientString); + + List ingredientParts = Arrays.stream(ingredientString.split(",")).map(String::trim).collect( + Collectors.toList()); + + + checkArgument(isValidName(ingredientParts.get(0)), MESSAGE_CONSTRAINTS); + checkArgument(isValidName(ingredientParts.get(1)), MESSAGE_CONSTRAINTS); + + this.name = ingredientParts.get(0); + this.quantity = ingredientParts.get(1); + // new Quantity(ingredientParts.get(1)); + } + + /** + * Returns {@code true} if {@code nameString} is not blank. + * + * @param nameString the {@link String} to be tested. + */ + private Boolean isValidName(String nameString) { + return !nameString.isBlank(); + } + + public String getQuantity() { + return this.quantity; + } + + public void setQuantity(String quantityString) { + this.quantity = quantityString; + // new Quantity(quantityString); + } + + @Override + public String toString() { + return this.name + ", " + this.quantity; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Ingredient // instanceof handles nulls + && name.equals(((Ingredient) other).name)); // state check + } + + @Override + public int hashCode() { + return name.hashCode(); + } +} diff --git a/src/main/java/cookbuddy/model/recipe/attribute/IngredientList.java b/src/main/java/cookbuddy/model/recipe/attribute/IngredientList.java new file mode 100644 index 00000000000..b7bae4b0751 --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/attribute/IngredientList.java @@ -0,0 +1,67 @@ +package cookbuddy.model.recipe.attribute; + +import static cookbuddy.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Represents a Recipe's ingredients in the recipe book. + */ +public class IngredientList { + public final ArrayList ingredientData; + + /** + * Constructs an ingredient list from {@code ingredientList}. Requires that the + * parameter be non-null. The parameter is guaranteed to be valid, as + * every item in the list has already been individually checked by + * {@link Ingredient#Ingredient(String)}. + * + * @param ingredientList a {@link List} of {@link Ingredient}s + */ + public IngredientList(List ingredientList) { + requireAllNonNull(ingredientList); + + this.ingredientData = new ArrayList(ingredientList); + } + + /** + * Adds an ingredient to the ingredient list. + * + * @param ingredient the ingredient to be added. + */ + public void addIngredient(Ingredient ingredient) { + ingredientData.add(ingredient); + } + + /** + * Deletes an ingredient from the ingredient list. + * + * @param ingredient the ingredient to be deleted. + */ + public void deleteIngredient(Ingredient ingredient) { + ingredientData.remove(ingredient); + } + + public List asList() { + return List.copyOf(this.ingredientData); + } + + @Override + public boolean equals(Object other) { + + return (other == this || other instanceof IngredientList + && ingredientData.equals(((IngredientList) other).ingredientData)); + } + + @Override + public int hashCode() { + return ingredientData.hashCode(); + } + + @Override + public String toString() { + return ingredientData.stream().map(Ingredient::toString).collect(Collectors.joining(";")); + } +} diff --git a/src/main/java/cookbuddy/model/recipe/attribute/Instruction.java b/src/main/java/cookbuddy/model/recipe/attribute/Instruction.java new file mode 100644 index 00000000000..192ab348365 --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/attribute/Instruction.java @@ -0,0 +1,54 @@ +package cookbuddy.model.recipe.attribute; + +import static cookbuddy.commons.util.AppUtil.checkArgument; +import static java.util.Objects.requireNonNull; + +/** + * Represents a Recipe's instruction in the recipe book. Guarantees: immutable; + * is valid as declared in {@link #isValidInstructions(String)} + */ +public class Instruction { + + public static final String MESSAGE_CONSTRAINTS = "Each instruction should be " + + "a non-blank string."; + + public final String instructionString; + + /** + * Constructs an {@code Instruction}. + * + * @param instructionString a valid, non-null {@link String} that represents a + * single line of instructions in a recipe. + */ + public Instruction(String instructionString) { + requireNonNull(instructionString); + checkArgument(isValidInstruction(instructionString), MESSAGE_CONSTRAINTS); + this.instructionString = instructionString; + } + + /** + * Returns true if {@code instructionString} is a valid instruction, as specified by + * {@link String#isBlank()}. + */ + public static boolean isValidInstruction(String instructionString) { + return !instructionString.isBlank(); + } + + @Override + public String toString() { + return this.instructionString; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Instruction // instanceof handles nulls + && instructionString.equals(((Instruction) other).instructionString)); // state check + } + + @Override + public int hashCode() { + return this.instructionString.hashCode(); + } + +} diff --git a/src/main/java/cookbuddy/model/recipe/attribute/InstructionList.java b/src/main/java/cookbuddy/model/recipe/attribute/InstructionList.java new file mode 100644 index 00000000000..529089002b9 --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/attribute/InstructionList.java @@ -0,0 +1,74 @@ +package cookbuddy.model.recipe.attribute; + +import static cookbuddy.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Represents a Recipe's instructions in the recipe book. + */ +public class InstructionList { + public final ArrayList instructionData; + + /** + * Constructs an instruction list from {@code ingredientList}. Requires that the + * parameter be non-null. The parameter is guaranteed to be valid, as every item + * in the list has already been individually checked by the Ingredient + * constructor. + * + * @param instructionList a {@link List} of {@link Instruction}s + */ + public InstructionList(List instructionList) { + requireAllNonNull(instructionList); + + this.instructionData = new ArrayList(instructionList); + } + + /** + * Adds an instruction from the instructions list. + * + * @param instruction the instruction to be added. + */ + public void addInstruction(Instruction instruction) { + instructionData.add(instruction); + } + + /** + * Deletes an instruction from the instructions list. + * + * @param instruction the instruction to be deleted. + */ + public void deleteInstruction(Instruction instruction) { + instructionData.remove(instruction); + } + + public List asList() { + return List.copyOf(this.instructionData); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof InstructionList)) { + return false; + } + + return ((InstructionList) other).instructionData.equals(this.instructionData); + } + + @Override + public int hashCode() { + return instructionData.hashCode(); + } + + @Override + public String toString() { + return instructionData.stream().map(Instruction::toString) + .collect(Collectors.joining(";")); + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/cookbuddy/model/recipe/attribute/Name.java similarity index 71% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/cookbuddy/model/recipe/attribute/Name.java index 79244d71cf7..5b36291fb01 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/cookbuddy/model/recipe/attribute/Name.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package cookbuddy.model.recipe.attribute; +import static cookbuddy.commons.util.AppUtil.checkArgument; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Recipe's name in the recipe book. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ public class Name { @@ -13,12 +13,12 @@ public class Name { "Names should only contain alphanumeric characters and spaces, and it should not be blank"; /* - * The first character of the address must not be a whitespace, + * The first character of the name must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; - public final String fullName; + private final String name; /** * Constructs a {@code Name}. @@ -28,7 +28,7 @@ public class Name { public Name(String name) { requireNonNull(name); checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); - fullName = name; + this.name = name; } /** @@ -38,22 +38,25 @@ public static boolean isValidName(String test) { return test.matches(VALIDATION_REGEX); } + public String getName() { + return name; + } @Override public String toString() { - return fullName; + return name; } @Override public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof Name // instanceof handles nulls - && fullName.equals(((Name) other).fullName)); // state check + && name.equals(((Name) other).name)); // state check } @Override public int hashCode() { - return fullName.hashCode(); + return name.hashCode(); } } diff --git a/src/main/java/cookbuddy/model/recipe/attribute/Photograph.java b/src/main/java/cookbuddy/model/recipe/attribute/Photograph.java new file mode 100644 index 00000000000..6b7d6253701 --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/attribute/Photograph.java @@ -0,0 +1,131 @@ +package cookbuddy.model.recipe.attribute; + +import static cookbuddy.commons.util.CollectionUtil.requireAllNonNull; +import static java.util.Objects.requireNonNull; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Path; + +import cookbuddy.commons.util.FileUtil; +import cookbuddy.commons.util.PhotographUtil; +import cookbuddy.model.recipe.Recipe; + +/** + * Wrapper class for {@link BufferedImage}. Represents a photo of a + * {@link Recipe} that a user can possibly provide to the application. + */ +public class Photograph { + + public static final PhotographUtil IMAGE_UTIL = PhotographUtil.imageUtil(); + public static final String MESSAGE_CONSTRAINTS = IMAGE_UTIL.messageConstraints; + public static final Photograph PLACEHOLDER_PHOTOGRAPH = new Photograph(IMAGE_UTIL.placeholderImage); + + private final BufferedImage photoData; + + /** + * Constructs a {@code Photograph}, by reading from {@code relativePath}, and + * parsing the file at the path. + * + * @param imagePath a comma-separated list of strings representing the path + * tree. + * @throws IOException + */ + public Photograph(String... imagePath) throws IOException { + this(FileUtil.absolutePathFrom(imagePath)); + } + + /** + * Constructs a {@code Photograph}, by reading from {@code imagePath}, and + * parsing the file at the path. + * + * @param imagePath A {@link Path} that directs to the photograph file in + * question. + * @throws IOException + */ + public Photograph(Path imagePath) throws IOException { + this(FileUtil.streamFromPath(imagePath)); + } + + /** + * Constructs a {@link Photograph} by reading from {@code imageInputStream}. + * + * @param imageInputStream The {@link InputStream} of this image; buffered and + * high-performance. + */ + public Photograph(InputStream imageInputStream) { + this(IMAGE_UTIL.getImage(imageInputStream)); + } + + private Photograph(BufferedImage image) { + requireNonNull(image); + this.photoData = image; + } + + /** + * Constructs a {@link Photograph} by reading data from {@code URL}. + * + * @param url The {@link URL} that this recipe's photograph is located at. + */ + public Photograph(URL url) { + requireAllNonNull(url); + this.photoData = IMAGE_UTIL.getImage(url); + } + + /** + * Returns the {@link Path} that {@code recipe}'s {@link Photograph} is to be + * stored as, for use by CookBuddy. Uses the recipe name to generate an image. + *

+ * If {@code recipe} only has a placeholder image, then a default path is + * returned. + * + * @param recipe The {@link Recipe} whose photograph file path is to be + * returned. + * @return The file name that this {@link Photograph} is to be stored on disk + * as. + */ + public Path getImageFileName(Recipe recipe) { + if (IMAGE_UTIL.isSameImage(this.photoData, IMAGE_UTIL.placeholderImage)) { + return FileUtil.relativePathFrom("placeholder"); + } else { + return FileUtil.relativePathFrom( + recipe.getName().toString().replaceAll("\\s+", "").toLowerCase() + "_" + this.hashCode() + ".png"); + } + } + + /** + * Returns an {@link InputStream} from this {@link Photograph}'s image data, by + * calling {@link PhotographUtil#getImageInputStream(BufferedImage)}. + * + * @return An {@link InputStream} of this {@link Photograph}, buffered and + * high-performance. + */ + public InputStream getInputStream() { + return IMAGE_UTIL.getImageInputStream(this.photoData); + } + + public BufferedImage getData() { + return this.photoData; + } + + @Override + public String toString() { + //return "Photograph@" + Integer.toHexString(this.photoData.hashCode()) + ": image =" + this.photoData + // .toString(); + return "Photograph successfully added from path"; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Photograph // instanceof handles nulls + && IMAGE_UTIL.isSameImage(this.photoData, ((Photograph) other).photoData)); // state check + } + + @Override + public int hashCode() { + return IMAGE_UTIL.hashImage(this.photoData); + } +} diff --git a/src/main/java/cookbuddy/model/recipe/attribute/Quantity.java b/src/main/java/cookbuddy/model/recipe/attribute/Quantity.java new file mode 100644 index 00000000000..14b0db2bd9a --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/attribute/Quantity.java @@ -0,0 +1,50 @@ +package cookbuddy.model.recipe.attribute; + +import static cookbuddy.commons.util.AppUtil.checkArgument; +import static java.util.Objects.requireNonNull; + +import java.util.regex.Pattern; + +/** + * A class to model quantities used in creating {@code Ingredient}s used in each + * recipe. + */ +public class Quantity { + public static final String MESSAGE_CONSTRAINTS = "Ingredient quantity must be a number" + + "followed by a unit; spaces are optional. E.g: 12 g"; + + public static final Pattern VALID_QUANTITY_PATTERN = Pattern.compile("\\d+(\\.(\\d+))? ?(\\w+)?"); + public static final Pattern VALID_NUMERIC_PATTERN = Pattern.compile("\\d+(\\.(\\d+))?"); + + private Float value; + private Unit unit; + + /** + * Constructs a Quantity from {@code quantityString}. + * + * @param quantityString a {@link String} that represents an + * {@link Ingredient}'s quantity. + */ + public Quantity(String quantityString) { + requireNonNull(quantityString); + checkArgument(isValidQuantity(quantityString), MESSAGE_CONSTRAINTS); + + this.value = Float.parseFloat(VALID_NUMERIC_PATTERN.matcher(quantityString).group(1)); + this.unit = new Unit(); + } + + /** + * Returns {@code true} if {@code quantityString} is a valid quantity String. + * + * @param quantityString a string representing an {@link Ingredient} quantity + */ + public static boolean isValidQuantity(String quantityString) { + return VALID_QUANTITY_PATTERN.matcher(quantityString).matches(); + } + + @Override + public String toString() { + // TODO Auto-generated method stub + return super.toString(); + } +} diff --git a/src/main/java/cookbuddy/model/recipe/attribute/Rating.java b/src/main/java/cookbuddy/model/recipe/attribute/Rating.java new file mode 100644 index 00000000000..13e33761293 --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/attribute/Rating.java @@ -0,0 +1,60 @@ +package cookbuddy.model.recipe.attribute; + +import static cookbuddy.commons.util.AppUtil.checkArgument; +import static java.util.Objects.requireNonNull; + +/** + * Represents the Rating of the recipe in the recipe book. + * Guarantees: immutable; rating is valid as declared in {@link #isValidRating(int)} + */ +public class Rating { + + public static final String MESSAGE_CONSTRAINTS = "Rating should be an integer >= 0 and <= 5"; + + public final int rating; + + /** + * Constructs a {@code Serving size}. + * + * @param rating A valid rating. + */ + public Rating(int rating) { + requireNonNull(rating); + checkArgument(isValidRating(rating), MESSAGE_CONSTRAINTS); + this.rating = rating; + } + + + /** + * Returns true if a given string is a valid serving size. + */ + public static boolean isValidRating(int test) { + return (test >= 0 && test <= 5); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Rating // instanceof handles nulls + && rating == (((Rating) other).rating)); // state check + } + + @Override + public int hashCode() { + return String.valueOf(rating).hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + String ratingString = ""; + for (int i = 0; i < rating; i++) { + ratingString += "\u2605"; + } + for (int i = rating; i < 5; i++) { + ratingString += "\u2606"; + } + return ratingString; + } +} diff --git a/src/main/java/cookbuddy/model/recipe/attribute/Serving.java b/src/main/java/cookbuddy/model/recipe/attribute/Serving.java new file mode 100644 index 00000000000..2be5831a249 --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/attribute/Serving.java @@ -0,0 +1,55 @@ +package cookbuddy.model.recipe.attribute; + +import static cookbuddy.commons.util.AppUtil.checkArgument; +import static java.util.Objects.requireNonNull; + +/** + * Represents the Serving size in the recipe book. + * Guarantees: immutable; serving is valid as declared in {@link #isValidServing(int)} + */ +public class Serving { + + public static final String MESSAGE_CONSTRAINTS = "Serving size should be > 0"; + + public final int serving; + + /** + * Constructs a {@code Serving size}. + * + * @param serving A valid serving size. + */ + public Serving(int serving) { + requireNonNull(serving); + checkArgument(isValidServing(serving), MESSAGE_CONSTRAINTS); + this.serving = serving; + } + + /** + * Returns true if a given string is a valid serving size. + */ + public static boolean isValidServing(int test) { + return test > 0; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Serving // instanceof handles nulls + && serving == (((Serving) other).serving)); // state check + } + + @Override + public int hashCode() { + return String.valueOf(serving).hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return "\uD83C\uDF74" + " : " + serving; + //return "\uD83C\uDF7D️\uD83C\uDF7D️\uD83C\uDF7D️"; + //return "\uD83C\uDF7D" + " : " + serving; + } + +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/cookbuddy/model/recipe/attribute/Tag.java similarity index 90% rename from src/main/java/seedu/address/model/tag/Tag.java rename to src/main/java/cookbuddy/model/recipe/attribute/Tag.java index b0ea7e7dad7..f0ea22e892d 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/cookbuddy/model/recipe/attribute/Tag.java @@ -1,10 +1,10 @@ -package seedu.address.model.tag; +package cookbuddy.model.recipe.attribute; +import static cookbuddy.commons.util.AppUtil.checkArgument; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Tag in the address book. + * Represents a Tag in the recipe book. * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} */ public class Tag { diff --git a/src/main/java/cookbuddy/model/recipe/attribute/Time.java b/src/main/java/cookbuddy/model/recipe/attribute/Time.java new file mode 100644 index 00000000000..8a75a8c3d19 --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/attribute/Time.java @@ -0,0 +1,115 @@ +package cookbuddy.model.recipe.attribute; + +import static cookbuddy.commons.util.AppUtil.checkArgument; +import static java.util.Objects.requireNonNull; + + +/** + * Represents a Recipe's time in the recipe book. + * Guarantees: immutable; is valid as declared in {@link #isValidHour(int)}, (@Link #isValidMin(int)}, + * {@Link #isValidSec(int)} + */ +public class Time { + + public static final String MESSAGE_CONSTRAINTS_HOUR = "The recipe should be between 0 and 72 hours long, inclusive"; + public static final String MESSAGE_CONSTRAINTS_MIN = "Mins should be < 60"; + public static final String MESSAGE_CONSTRAINTS_SEC = "Secs should be < 60"; + + + + private int hour; + private int min; + private int sec; + + /** + * Constructs a {@code Time}. + * + * @param hour A valid hour. + * @param min A valid minute. + * @param sec A valid second. + */ + public Time(int hour, int min, int sec) { + requireNonNull(hour); + requireNonNull(min); + requireNonNull(sec); + checkArgument(isValidHour(hour), MESSAGE_CONSTRAINTS_HOUR); + checkArgument(isValidMin(min), MESSAGE_CONSTRAINTS_MIN); + checkArgument(isValidSec(sec), MESSAGE_CONSTRAINTS_SEC); + this.hour = hour; + this.min = min; + this.sec = sec; + } + + /** + * Returns true if a given string is a valid name. + */ + + public static boolean isValidHour(int test) { + return (test >= 0 && test <= 72); + } + + public static boolean isValidMin(int test) { + return (test >= 0 && test < 60); + } + + public static boolean isValidSec(int test) { + return (test >= 0 && test < 60); + } + + public void setTime(Time preptime) { + hour = preptime.getHour(); + min = preptime.getMin(); + sec = preptime.getSec(); + } + + public int getHour() { + return hour; + } + + public int getMin() { + return min; + } + + public int getSec() { + return sec; + } + + @Override + public String toString() { + String toReturn = ""; + if ((hour + min + sec) == 0) { + toReturn += "-"; + } else { + if (hour < 10) { + toReturn += "0"; + } + toReturn += String.valueOf(hour); + toReturn += ":"; + if (min < 10) { + toReturn += "0"; + } + toReturn += String.valueOf(min); + toReturn += ":"; + if (sec < 10) { + toReturn += "0"; + } + toReturn += String.valueOf(sec); + } + return toReturn; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Time // instanceof handles nulls + && hour == (((Time) other).hour)) + && min == (((Time) other).min) + && sec == (((Time) other).sec); // state check + } + + @Override + public int hashCode() { + return String.valueOf(hour + min + sec).hashCode(); + } + +} diff --git a/src/main/java/cookbuddy/model/recipe/attribute/Unit.java b/src/main/java/cookbuddy/model/recipe/attribute/Unit.java new file mode 100644 index 00000000000..ef0a0f05df4 --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/attribute/Unit.java @@ -0,0 +1,14 @@ +package cookbuddy.model.recipe.attribute; + +/** + * A unit class to model units for each {@code Quantity}. + */ + +public class Unit { + /** + * UnitSymbol + */ + public enum UnitSymbol { + + } +} diff --git a/src/main/java/cookbuddy/model/recipe/exceptions/DuplicateRecipeException.java b/src/main/java/cookbuddy/model/recipe/exceptions/DuplicateRecipeException.java new file mode 100644 index 00000000000..d787765df86 --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/exceptions/DuplicateRecipeException.java @@ -0,0 +1,12 @@ +package cookbuddy.model.recipe.exceptions; + +/** + * Signals that the operation will result in duplicate Recipes (Recipes are considered duplicates if they have the same + * identity). + */ +@SuppressWarnings("serial") +public class DuplicateRecipeException extends RuntimeException { + public DuplicateRecipeException() { + super("Operation would result in duplicate recipes"); + } +} diff --git a/src/main/java/cookbuddy/model/recipe/exceptions/RecipeNotFoundException.java b/src/main/java/cookbuddy/model/recipe/exceptions/RecipeNotFoundException.java new file mode 100644 index 00000000000..8225136caaf --- /dev/null +++ b/src/main/java/cookbuddy/model/recipe/exceptions/RecipeNotFoundException.java @@ -0,0 +1,7 @@ +package cookbuddy.model.recipe.exceptions; + +/** + * Signals that the operation is unable to find the specified recipe. + */ +@SuppressWarnings("serial") +public class RecipeNotFoundException extends RuntimeException {} diff --git a/src/main/java/cookbuddy/model/util/SampleDataUtil.java b/src/main/java/cookbuddy/model/util/SampleDataUtil.java new file mode 100644 index 00000000000..2702f4b07a6 --- /dev/null +++ b/src/main/java/cookbuddy/model/util/SampleDataUtil.java @@ -0,0 +1,77 @@ +package cookbuddy.model.util; + +import static cookbuddy.commons.util.FileUtil.getResourceAsInputStream; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import cookbuddy.model.ReadOnlyRecipeBook; +import cookbuddy.model.RecipeBook; +import cookbuddy.model.recipe.Recipe; +import cookbuddy.model.recipe.attribute.Calorie; +import cookbuddy.model.recipe.attribute.Difficulty; +import cookbuddy.model.recipe.attribute.Ingredient; +import cookbuddy.model.recipe.attribute.IngredientList; +import cookbuddy.model.recipe.attribute.Instruction; +import cookbuddy.model.recipe.attribute.InstructionList; +import cookbuddy.model.recipe.attribute.Name; +import cookbuddy.model.recipe.attribute.Photograph; +import cookbuddy.model.recipe.attribute.Rating; +import cookbuddy.model.recipe.attribute.Serving; +import cookbuddy.model.recipe.attribute.Tag; + +/** + * Contains utility methods for populating {@code RecipeBook} with sample data. + */ +public class SampleDataUtil { + + public static Recipe[] getSampleRecipes() { + Name name1 = new Name("Ham Sandwich"); + IngredientList ingList1 = new IngredientList( + List.of(new Ingredient("bread, 2 slices"), new Ingredient("ham, 1 slice"))); + InstructionList insList1 = new InstructionList( + List.of(new Instruction("put ham between bread"), new Instruction("serve on plate"))); + Photograph image1 = new Photograph(getResourceAsInputStream("/images/hamsandwich_recipe.jpg")); + Calorie calorie1 = new Calorie("169"); + Serving serving1 = new Serving(3); + Rating rating1 = new Rating(2); + Difficulty difficulty1 = new Difficulty(3); + Set tagSet1 = getTagSet("breakfast", "lunch"); + + Recipe recipe1 = new Recipe(name1, ingList1, insList1, image1, calorie1, serving1, rating1, difficulty1, + tagSet1); + + Name name2 = new Name("Idiot Sandwich"); + IngredientList ingList2 = new IngredientList(List.of(new Ingredient("bread, 2 slices"))); + InstructionList insList2 = new InstructionList(List.of(new Instruction("put bread to opposite sides of head"), + new Instruction("Yell 'I am an idiot sandwich!'"))); + Photograph image2 = new Photograph(getResourceAsInputStream("/images/idiotsandwich_recipe.jpg")); + Calorie calorie2 = new Calorie("0"); + Serving serving2 = new Serving(2); + Rating rating2 = new Rating(4); + Difficulty difficulty2 = new Difficulty(1); + Set tagSet2 = getTagSet("lunch", "dinner"); + + Recipe recipe2 = new Recipe(name2, ingList2, insList2, image2, calorie2, serving2, rating2, difficulty2, + tagSet2); + + return new Recipe[] { recipe1, recipe2 }; + } + + public static ReadOnlyRecipeBook getSampleRecipeBook() { + RecipeBook sampleAb = new RecipeBook(); + for (Recipe sampleRecipe : getSampleRecipes()) { + sampleAb.addRecipe(sampleRecipe); + } + 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/cookbuddy/storage/JsonAdaptedRecipe.java b/src/main/java/cookbuddy/storage/JsonAdaptedRecipe.java new file mode 100644 index 00000000000..e3d149353e7 --- /dev/null +++ b/src/main/java/cookbuddy/storage/JsonAdaptedRecipe.java @@ -0,0 +1,172 @@ +package cookbuddy.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import cookbuddy.commons.exceptions.IllegalValueException; +import cookbuddy.commons.util.FileUtil; +import cookbuddy.logic.parser.ParserUtil; +import cookbuddy.model.recipe.Recipe; +import cookbuddy.model.recipe.attribute.Calorie; +import cookbuddy.model.recipe.attribute.Difficulty; +import cookbuddy.model.recipe.attribute.IngredientList; +import cookbuddy.model.recipe.attribute.InstructionList; +import cookbuddy.model.recipe.attribute.Name; +import cookbuddy.model.recipe.attribute.Photograph; +import cookbuddy.model.recipe.attribute.Rating; +import cookbuddy.model.recipe.attribute.Serving; +import cookbuddy.model.recipe.attribute.Tag; + +/** + * Jackson-friendly version of {@link Recipe}. + */ +class JsonAdaptedRecipe { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Recipe's %s field is missing!"; + + private final String name; + private final String ingredients; + private final String instructions; + private final Path imageFilePath; + private final String calorie; + private final int serving; + private final int rating; + private final int difficulty; + private final String fav; + private final String done; + private final String time; + private final List tagged = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedRecipe} with the given recipe details. + */ + @JsonCreator + public JsonAdaptedRecipe(@JsonProperty("name") String name, @JsonProperty("ingredients") String ingredients, + @JsonProperty("instructions") String instructions, @JsonProperty("filePath") Path imageFilePath, + @JsonProperty("calorie") String calorie, @JsonProperty("serving") int serving, + @JsonProperty("rating") int rating, @JsonProperty("difficulty") int difficulty, + @JsonProperty("fav") String fav, @JsonProperty("done") String done, @JsonProperty("time") String time, + @JsonProperty("tagged") List tagged) { + this.name = name; + this.ingredients = ingredients; + this.instructions = instructions; + this.imageFilePath = imageFilePath; + this.calorie = calorie; + this.serving = serving; + this.rating = rating; + this.fav = fav; + this.done = done; + this.time = time; + this.difficulty = difficulty; + if (tagged != null) { + this.tagged.addAll(tagged); + } + } + + /** + * Converts a given {@code Recipe} into this class for Jackson use. + */ + public JsonAdaptedRecipe(Recipe source) { + name = source.getName().toString(); + ingredients = source.getIngredients().toString(); + instructions = source.getInstructions().toString(); + imageFilePath = source.getPhotograph().getImageFileName(source); + calorie = source.getCalorie().calorie; + serving = source.getServing().serving; + rating = source.getRating().rating; + difficulty = source.getDifficulty().difficulty; + fav = source.getFavStatus().toString(); + done = source.getDoneStatus().toString(); + time = source.getPrepTime().toString(); + tagged.addAll(source.getTags().stream().map(JsonAdaptedTag::new).collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted recipe object into the model's + * {@code Recipe} object. + * + * @throws IllegalValueException if there were any data constraints violated in + * the adapted recipe. + */ + public Recipe toModelType(Path imageStoragePath) throws IllegalValueException { + final List recipeTags = new ArrayList<>(); + for (JsonAdaptedTag tag : tagged) { + recipeTags.add(tag.toModelType()); + } + + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final Name modelName = new Name(name); + + if (ingredients == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, IngredientList.class.getSimpleName())); + } + // if (!IngredientList.isValidIngredients(ingredients)) { + // throw new IllegalValueException(IngredientList.MESSAGE_CONSTRAINTS); + // } + final IngredientList modelIngredients = ParserUtil.parseIngredients(ingredients); + + if (instructions == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, InstructionList.class.getSimpleName())); + } + // if (!InstructionList.isValidInstructions(instructions)) { + // throw new IllegalValueException(InstructionList.MESSAGE_CONSTRAINTS); + // } + final InstructionList modelInstructions = ParserUtil.parseInstructions(instructions); + + if (imageFilePath == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, Photograph.class.getSimpleName())); + } + + if (!FileUtil.isValidPathString(imageFilePath.toString())) { + throw new IllegalValueException(Photograph.MESSAGE_CONSTRAINTS); + } + + Photograph modelPhotograph; + try { + modelPhotograph = Photograph.IMAGE_UTIL.isPlaceHolderImage( + FileUtil.joinPaths(imageStoragePath, imageFilePath)) ? Photograph.PLACEHOLDER_PHOTOGRAPH + : new Photograph(FileUtil.joinPaths(imageStoragePath, imageFilePath)); + } catch (IOException e) { + modelPhotograph = Photograph.PLACEHOLDER_PHOTOGRAPH; + } + + final Calorie modelCalorie = new Calorie(calorie); + final Serving modelServe = new Serving(serving); + final Rating modelRating = new Rating(rating); + final Difficulty modelDifficulty = new Difficulty(difficulty); + final Set modelTags = new HashSet<>(recipeTags); + + Recipe toReturn = new Recipe(modelName, modelIngredients, modelInstructions, modelPhotograph, modelCalorie, + modelServe, modelRating, modelDifficulty, modelTags); + + if (fav.equals("\u2665")) { + toReturn.favRecipe(); + } + + if (done.equals("Yes")) { + toReturn.attemptRecipe(); + } + + if (!time.equals("-")) { + toReturn.setTime(ParserUtil.parseTime(time)); + } + + return toReturn; + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/cookbuddy/storage/JsonAdaptedTag.java similarity index 89% rename from src/main/java/seedu/address/storage/JsonAdaptedTag.java rename to src/main/java/cookbuddy/storage/JsonAdaptedTag.java index 0df22bdb754..df8b9b44a39 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/cookbuddy/storage/JsonAdaptedTag.java @@ -1,10 +1,10 @@ -package seedu.address.storage; +package cookbuddy.storage; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; +import cookbuddy.commons.exceptions.IllegalValueException; +import cookbuddy.model.recipe.attribute.Tag; /** * Jackson-friendly version of {@link Tag}. diff --git a/src/main/java/cookbuddy/storage/JsonRecipeBookStorage.java b/src/main/java/cookbuddy/storage/JsonRecipeBookStorage.java new file mode 100644 index 00000000000..97dd7182f20 --- /dev/null +++ b/src/main/java/cookbuddy/storage/JsonRecipeBookStorage.java @@ -0,0 +1,81 @@ +package cookbuddy.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import cookbuddy.commons.core.LogsCenter; +import cookbuddy.commons.exceptions.DataConversionException; +import cookbuddy.commons.exceptions.IllegalValueException; +import cookbuddy.commons.util.FileUtil; +import cookbuddy.commons.util.JsonUtil; +import cookbuddy.commons.util.PhotographUtil; +import cookbuddy.model.ReadOnlyRecipeBook; + +/** + * A class to access RecipeBook data stored as a json file on the hard disk. + */ +public class JsonRecipeBookStorage implements RecipeBookStorage { + + private static final Logger logger = LogsCenter.getLogger(JsonRecipeBookStorage.class); + + private Path filePath; + private Path imagesPath; + + public JsonRecipeBookStorage(Path filePath, Path imagesPath) { + this.filePath = filePath; + this.imagesPath = imagesPath; + } + + public Path getRecipeBookFilePath() { + return filePath; + } + + @Override + public Optional readRecipeBook() throws DataConversionException { + return readRecipeBook(filePath); + } + + /** + * Similar to {@link #readRecipeBook()}. + * + * @param filePath location of the data. Cannot be null. + * @throws DataConversionException if the file is not in the correct format. + */ + public Optional readRecipeBook(Path filePath) throws DataConversionException { + requireNonNull(filePath); + + Optional jsonRecipeBook = JsonUtil.readJsonFile( + filePath, JsonSerializableRecipeBook.class); + if (!jsonRecipeBook.isPresent()) { + return Optional.empty(); + } + + try { + return Optional.of(jsonRecipeBook.get().toModelType(imagesPath)); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataConversionException(ive); + } + } + + @Override + public void saveRecipeBook(ReadOnlyRecipeBook recipeBook) throws IOException { + saveRecipeBook(recipeBook, filePath); + } + + /** + * Similar to {@link #saveRecipeBook(ReadOnlyRecipeBook)}. + */ + public void saveRecipeBook(ReadOnlyRecipeBook recipeBook, Path dataFilePath) throws IOException { + requireNonNull(recipeBook); + requireNonNull(dataFilePath); + + FileUtil.createIfMissing(dataFilePath); + JsonUtil.saveJsonFile(new JsonSerializableRecipeBook(recipeBook), dataFilePath); + PhotographUtil.imageUtil().saveAllImages(recipeBook.getRecipeList(), imagesPath); + } +} diff --git a/src/main/java/cookbuddy/storage/JsonSerializableRecipeBook.java b/src/main/java/cookbuddy/storage/JsonSerializableRecipeBook.java new file mode 100644 index 00000000000..82eb98b8b64 --- /dev/null +++ b/src/main/java/cookbuddy/storage/JsonSerializableRecipeBook.java @@ -0,0 +1,61 @@ +package cookbuddy.storage; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import cookbuddy.commons.exceptions.IllegalValueException; +import cookbuddy.model.ReadOnlyRecipeBook; +import cookbuddy.model.RecipeBook; +import cookbuddy.model.recipe.Recipe; + +/** + * An Immutable RecipeBook that is serializable to JSON format. + */ +@JsonRootName(value = "recipeBook") +class JsonSerializableRecipeBook { + + public static final String MESSAGE_DUPLICATE_RECIPE = "Recipe list contains duplicate recipe(s)."; + + private final List recipes = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializableRecipeBook} with the given recipes. + */ + @JsonCreator + public JsonSerializableRecipeBook(@JsonProperty("recipes") List recipes) { + this.recipes.addAll(recipes); + } + + /** + * Converts a given {@code ReadOnlyRecipeBook} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializableRecipeBook}. + */ + public JsonSerializableRecipeBook(ReadOnlyRecipeBook source) { + recipes.addAll(source.getRecipeList().stream().map(JsonAdaptedRecipe::new).collect(Collectors.toList())); + } + + /** + * Converts this recipe book into the model's {@code RecipeBook} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public RecipeBook toModelType(Path imagesPath) throws IllegalValueException { + RecipeBook recipeBook = new RecipeBook(); + for (JsonAdaptedRecipe jsonAdaptedRecipe : recipes) { + Recipe recipe = jsonAdaptedRecipe.toModelType(imagesPath); + if (recipeBook.hasRecipe(recipe)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_RECIPE); + } + recipeBook.addRecipe(recipe); + } + return recipeBook; + } + +} diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/cookbuddy/storage/JsonUserPrefsStorage.java similarity index 83% rename from src/main/java/seedu/address/storage/JsonUserPrefsStorage.java rename to src/main/java/cookbuddy/storage/JsonUserPrefsStorage.java index bc2bbad84aa..894671275de 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java +++ b/src/main/java/cookbuddy/storage/JsonUserPrefsStorage.java @@ -1,13 +1,13 @@ -package seedu.address.storage; +package cookbuddy.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import cookbuddy.commons.exceptions.DataConversionException; +import cookbuddy.commons.util.JsonUtil; +import cookbuddy.model.ReadOnlyUserPrefs; +import cookbuddy.model.UserPrefs; /** * A class to access UserPrefs stored in the hard disk as a json file diff --git a/src/main/java/cookbuddy/storage/RecipeBookStorage.java b/src/main/java/cookbuddy/storage/RecipeBookStorage.java new file mode 100644 index 00000000000..7b4eab7caa8 --- /dev/null +++ b/src/main/java/cookbuddy/storage/RecipeBookStorage.java @@ -0,0 +1,45 @@ +package cookbuddy.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import cookbuddy.commons.exceptions.DataConversionException; +import cookbuddy.model.ReadOnlyRecipeBook; +import cookbuddy.model.RecipeBook; + +/** + * Represents a storage for {@link RecipeBook}. + */ +public interface RecipeBookStorage { + + /** + * Returns the file path of the data file. + */ + Path getRecipeBookFilePath(); + + /** + * Returns RecipeBook data as a {@link ReadOnlyRecipeBook}. + * Returns {@code Optional.empty()} if storage file is not found. + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional readRecipeBook() throws DataConversionException, IOException; + + /** + * @see #getRecipeBookFilePath() + */ + Optional readRecipeBook(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyRecipeBook} to the storage. + * @param recipeBook cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveRecipeBook(ReadOnlyRecipeBook recipeBook) throws IOException; + + /** + * @see #saveRecipeBook(ReadOnlyRecipeBook) + */ + void saveRecipeBook(ReadOnlyRecipeBook recipeBook, Path filePath) throws IOException; +} diff --git a/src/main/java/cookbuddy/storage/Storage.java b/src/main/java/cookbuddy/storage/Storage.java new file mode 100644 index 00000000000..ccdbb1d1638 --- /dev/null +++ b/src/main/java/cookbuddy/storage/Storage.java @@ -0,0 +1,32 @@ +package cookbuddy.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import cookbuddy.commons.exceptions.DataConversionException; +import cookbuddy.model.ReadOnlyRecipeBook; +import cookbuddy.model.ReadOnlyUserPrefs; +import cookbuddy.model.UserPrefs; + +/** + * API of the Storage component + */ +public interface Storage extends RecipeBookStorage, UserPrefsStorage { + + @Override + Optional readUserPrefs() throws DataConversionException, IOException; + + @Override + void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; + + @Override + Path getRecipeBookFilePath(); + + @Override + Optional readRecipeBook() throws DataConversionException, IOException; + + @Override + void saveRecipeBook(ReadOnlyRecipeBook recipeBook) throws IOException; + +} diff --git a/src/main/java/cookbuddy/storage/StorageManager.java b/src/main/java/cookbuddy/storage/StorageManager.java new file mode 100644 index 00000000000..eb759d5ebed --- /dev/null +++ b/src/main/java/cookbuddy/storage/StorageManager.java @@ -0,0 +1,76 @@ +package cookbuddy.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import cookbuddy.commons.core.LogsCenter; +import cookbuddy.commons.exceptions.DataConversionException; +import cookbuddy.model.ReadOnlyRecipeBook; +import cookbuddy.model.ReadOnlyUserPrefs; +import cookbuddy.model.UserPrefs; + +/** + * Manages storage of RecipeBook data in local storage. + */ +public class StorageManager implements Storage { + + private static final Logger logger = LogsCenter.getLogger(StorageManager.class); + private RecipeBookStorage recipeBookStorage; + private UserPrefsStorage userPrefsStorage; + + + public StorageManager(RecipeBookStorage recipeBookStorage, UserPrefsStorage userPrefsStorage) { + super(); + this.recipeBookStorage = recipeBookStorage; + this.userPrefsStorage = userPrefsStorage; + } + + // ================ UserPrefs methods ============================== + + @Override + public Path getUserPrefsFilePath() { + return userPrefsStorage.getUserPrefsFilePath(); + } + + @Override + public Optional readUserPrefs() throws DataConversionException, IOException { + return userPrefsStorage.readUserPrefs(); + } + + @Override + public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { + userPrefsStorage.saveUserPrefs(userPrefs); + } + + + // ================ RecipeBook methods ============================== + + @Override + public Path getRecipeBookFilePath() { + return recipeBookStorage.getRecipeBookFilePath(); + } + + @Override + public Optional readRecipeBook() throws DataConversionException, IOException { + return readRecipeBook(recipeBookStorage.getRecipeBookFilePath()); + } + + @Override + public Optional readRecipeBook(Path filePath) throws DataConversionException, IOException { + logger.fine("Attempting to read data from file: " + filePath); + return recipeBookStorage.readRecipeBook(filePath); + } + + @Override + public void saveRecipeBook(ReadOnlyRecipeBook recipeBook) throws IOException { + saveRecipeBook(recipeBook, recipeBookStorage.getRecipeBookFilePath()); + } + + @Override + public void saveRecipeBook(ReadOnlyRecipeBook recipeBook, Path filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + recipeBookStorage.saveRecipeBook(recipeBook, filePath); + } +} diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/cookbuddy/storage/UserPrefsStorage.java similarity index 71% rename from src/main/java/seedu/address/storage/UserPrefsStorage.java rename to src/main/java/cookbuddy/storage/UserPrefsStorage.java index 29eef178dbc..a061c9ff128 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/cookbuddy/storage/UserPrefsStorage.java @@ -1,15 +1,15 @@ -package seedu.address.storage; +package cookbuddy.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import cookbuddy.commons.exceptions.DataConversionException; +import cookbuddy.model.ReadOnlyUserPrefs; +import cookbuddy.model.UserPrefs; /** - * Represents a storage for {@link seedu.address.model.UserPrefs}. + * Represents a storage for {@link cookbuddy.model.UserPrefs}. */ public interface UserPrefsStorage { @@ -27,7 +27,7 @@ public interface UserPrefsStorage { Optional readUserPrefs() throws DataConversionException, IOException; /** - * Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage. + * Saves the given {@link cookbuddy.model.ReadOnlyUserPrefs} to the storage. * @param userPrefs cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/cookbuddy/ui/CommandBox.java b/src/main/java/cookbuddy/ui/CommandBox.java new file mode 100644 index 00000000000..2b7b7891e7b --- /dev/null +++ b/src/main/java/cookbuddy/ui/CommandBox.java @@ -0,0 +1,107 @@ +package cookbuddy.ui; + +import cookbuddy.logic.CommandHistory; +import cookbuddy.logic.commands.CommandResult; +import cookbuddy.logic.commands.exceptions.CommandException; +import cookbuddy.logic.parser.exceptions.ParseException; +import javafx.collections.ObservableList; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.scene.control.TextArea; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.Region; + +/** + * The UI component that is responsible for receiving user command inputs. + */ +public class CommandBox extends UiPart { + + public static final String ERROR_STYLE_CLASS = "error"; + private static final String FXML = "CommandBox.fxml"; + + private final CommandHistory commandHistory = new CommandHistory(); + private final CommandExecutor commandExecutor; + + @FXML + private TextArea commandTextArea; + + public CommandBox(CommandExecutor commandExecutor) { + super(FXML); + this.commandExecutor = commandExecutor; + setHandler(); + // calls #setStyleToDefault() whenever there is a change to the text of the + commandTextArea.textProperty().addListener((unused1, unused2, unused3) -> setStyleToMonospace()); + } + + /** + * Handles the Enter button pressed event. + */ + private void setHandler() { + this.commandTextArea.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler() { + @Override + public void handle(KeyEvent keyEvent) { + String commandString; + if (keyEvent.getCode().equals(KeyCode.ENTER)) { + commandString = commandTextArea.getText(); + try { + commandExecutor.execute(commandString); + commandHistory.addCommand(commandString); + commandHistory.resetIterator(); + commandTextArea.setText(""); + } catch (ParseException | CommandException e) { + ; + } finally { + keyEvent.consume(); + } + } else if (keyEvent.getCode().equals(KeyCode.PAGE_UP)) { + String text = commandHistory.iterateNext(); + if (text != null) { + commandTextArea.setText(text); + } + } else if (keyEvent.getCode().equals(KeyCode.PAGE_DOWN)) { + String text = commandHistory.iteratePrevious(); + if (text != null) { + commandTextArea.setText(text); + } + } + } + }); + } + + /** + * Sets the command box style to use the default style: monospace black. + */ + @FXML + private void setStyleToMonospace() { + commandTextArea.setStyle("-fx-font-family: Consolas, 'Menlo', 'Hack', 'Liberation Mono', 'monospace';"); + commandTextArea.setStyle("-fx-text-fill: black;"); + } + + /** + * Sets the command box style to indicate a failed command. + */ + private void setStyleToIndicateCommandFailure() { + ObservableList styleClass = commandTextArea.getStyleClass(); + + if (styleClass.contains(ERROR_STYLE_CLASS)) { + return; + } + + styleClass.add(ERROR_STYLE_CLASS); + } + + /** + * Represents a function that can execute commands. + */ + @FunctionalInterface + public interface CommandExecutor { + /** + * Executes the command and returns the result. + * + * @see cookbuddy.logic.Logic#execute(String) + */ + CommandResult execute(String commandText) throws CommandException, ParseException; + } + +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/cookbuddy/ui/HelpWindow.java similarity index 75% rename from src/main/java/seedu/address/ui/HelpWindow.java rename to src/main/java/cookbuddy/ui/HelpWindow.java index 9a665915949..96487b7737f 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/cookbuddy/ui/HelpWindow.java @@ -1,21 +1,23 @@ -package seedu.address.ui; +package cookbuddy.ui; import java.util.logging.Logger; +import cookbuddy.commons.core.LogsCenter; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; import javafx.stage.Stage; -import seedu.address.commons.core.LogsCenter; +import jfxtras.styles.jmetro.JMetro; +import jfxtras.styles.jmetro.Style; /** * 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 USERGUIDE_URL = "https://ay1920s2-cs2103t-w12-4.github.io/main/UserGuide.html"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); @@ -37,11 +39,13 @@ public HelpWindow(Stage root) { helpMessage.setText(HELP_MESSAGE); } + /** * Creates a new HelpWindow. */ public HelpWindow() { this(new Stage()); + new JMetro(Style.LIGHT).setScene(this.getRoot().getScene()); } /** @@ -68,6 +72,19 @@ public void show() { getRoot().centerOnScreen(); } + /** + * Displays the help window + * @param toBeShown the message to be displayed in the help window. + */ + public void show(String toBeShown) { + if (toBeShown.equals("")) { + helpMessage.setText(HELP_MESSAGE); + } else { + helpMessage.setText(toBeShown); + } + show(); + } + /** * Returns true if the help window is currently being shown. */ @@ -89,6 +106,10 @@ public void focus() { getRoot().requestFocus(); } + public void setCommandDescription(String commandDescription) { + helpMessage.setText(commandDescription); + } + /** * Copies the URL to the user guide to the clipboard. */ diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/cookbuddy/ui/MainWindow.java similarity index 68% rename from src/main/java/seedu/address/ui/MainWindow.java rename to src/main/java/cookbuddy/ui/MainWindow.java index 90bbf11de97..43c2f8e679b 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/cookbuddy/ui/MainWindow.java @@ -1,7 +1,14 @@ -package seedu.address.ui; +package cookbuddy.ui; import java.util.logging.Logger; +import cookbuddy.commons.core.GuiSettings; +import cookbuddy.commons.core.LogsCenter; +import cookbuddy.logic.Logic; +import cookbuddy.logic.commands.CommandResult; +import cookbuddy.logic.commands.exceptions.CommandException; +import cookbuddy.logic.parser.exceptions.ParseException; +import cookbuddy.model.recipe.Recipe; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.MenuItem; @@ -10,12 +17,8 @@ import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; import javafx.stage.Stage; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.Logic; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; +import jfxtras.styles.jmetro.JMetro; +import jfxtras.styles.jmetro.Style; /** * The Main Window. Provides the basic application layout containing @@ -23,7 +26,11 @@ */ public class MainWindow extends UiPart { + public static final JMetro JMETRO = new JMetro(Style.LIGHT); + + private static final String FXML = "MainWindow.fxml"; + private static String commandDescription = ""; private final Logger logger = LogsCenter.getLogger(getClass()); @@ -31,9 +38,11 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; + private RecipeListPanel recipeListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; + private RecipeView recipeView; + @FXML private StackPane commandBoxPlaceholder; @@ -42,7 +51,10 @@ public class MainWindow extends UiPart { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane recipeListPanelPlaceholder; + + @FXML + private StackPane recipeViewPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -60,6 +72,9 @@ public MainWindow(Stage primaryStage, Logic logic) { // Configure the UI setWindowDefaultSize(logic.getGuiSettings()); + JMETRO.setAutomaticallyColorPanes(true); + JMETRO.setScene(this.primaryStage.getScene()); + setAccelerators(); helpWindow = new HelpWindow(); @@ -75,6 +90,7 @@ private void setAccelerators() { /** * Sets the accelerator of a MenuItem. + * * @param keyCombination the KeyCombination value of the accelerator */ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { @@ -106,20 +122,47 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { /** * Fills up all the placeholders of this window. */ - void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + public void defaultFill(Recipe recipe) { + if (logic.getFilteredRecipeList().size() == 0) { + recipeView = new RecipeView(); + } else { + recipeView = new RecipeView(recipe); + } + fillInfo(); + } + + + /** + * fills in the ingredient/instruction fields + */ + public void fillInfo() { + this.recipeViewPanelPlaceholder.getChildren().add(recipeView.getRoot()); + + recipeListPanel = new RecipeListPanel(logic.getFilteredRecipeList()); + recipeListPanelPlaceholder.getChildren().add(recipeListPanel.getRoot()); resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getRecipeBookFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(this::executeCommand); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); } + /** + * Fills in information for the recipe + */ + public void fillInnerParts() { + if (logic.getFilteredRecipeList().size() == 0) { + recipeView = new RecipeView(); + fillInfo(); + } else { + defaultFill(logic.getFilteredRecipeList().get(0)); + } + } + /** * Sets the default size based on {@code guiSettings}. */ @@ -132,11 +175,26 @@ private void setWindowDefaultSize(GuiSettings guiSettings) { } } + /** + * Sets the command description of the help window + * @param commandDescription the new command description. + */ + public static void setCommandDescription(String commandDescription) { + MainWindow.commandDescription = commandDescription; + } + /** * Opens the help window or focuses on it if it's already opened. */ @FXML public void handleHelp() { + String toDisplay = UiManager.getCommandDescription(); + helpWindow = new HelpWindow(); + if (!toDisplay.equals("")) { + helpWindow.show(toDisplay); + } else { + helpWindow.show(HelpWindow.HELP_MESSAGE); + } if (!helpWindow.isShowing()) { helpWindow.show(); } else { @@ -154,20 +212,20 @@ void show() { @FXML private void handleExit() { GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), - (int) primaryStage.getX(), (int) primaryStage.getY()); + (int) primaryStage.getX(), (int) primaryStage.getY()); logic.setGuiSettings(guiSettings); helpWindow.hide(); primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + public RecipeListPanel getPersonListPanel() { + return recipeListPanel; } /** * Executes the command and returns the result. * - * @see seedu.address.logic.Logic#execute(String) + * @see cookbuddy.logic.Logic#execute(String) */ private CommandResult executeCommand(String commandText) throws CommandException, ParseException { try { diff --git a/src/main/java/cookbuddy/ui/RecipeCard.java b/src/main/java/cookbuddy/ui/RecipeCard.java new file mode 100644 index 00000000000..fd8ae6a4755 --- /dev/null +++ b/src/main/java/cookbuddy/ui/RecipeCard.java @@ -0,0 +1,97 @@ +package cookbuddy.ui; + +import java.util.Comparator; + +import cookbuddy.model.recipe.Recipe; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; + +/** + * An UI component that displays information of a {@code Recipe}. + */ +public class RecipeCard extends UiPart { + + private static final String FXML = "RecipeListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on RecipeBook level 4 + */ + + public final Recipe recipe; + + @FXML + private VBox cardPane; + @FXML + private HBox title; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label ingredients; + @FXML + private Label instructions; + @FXML + private Label calorie; + @FXML + private Label fav; + @FXML + private Label done; + @FXML + private Label timing; + @FXML + private Label serving; + @FXML + private Label rating; + @FXML + private Label diff; + @FXML + private FlowPane tags; + public RecipeCard(Recipe recipe, int displayedIndex) { + super(FXML); + this.recipe = recipe; + + this.cardPane.setStyle("-fx-background-color: transparent;"); + this.title.setStyle("-fx-background-color: transparent;"); + this.tags.setStyle("-fx-background-color: transparent;"); + + id.setText(displayedIndex + ". "); + name.setText(recipe.getName().toString()); + calorie.setText("Calorie: " + recipe.getCalorie().toString() + " kcal"); + fav.setText(recipe.getFavStatus().toString()); + done.setText("Attempted: " + recipe.getDoneStatus().toString()); + timing.setText("\uD83D\uDD52: " + recipe.getPrepTime().toString()); + serving.setText(recipe.getServing().toString()); + rating.setText(recipe.getRating().toString()); + diff.setText("Difficulty: " + recipe.getDifficulty().toString()); + recipe.getTags().stream() + .sorted(Comparator.comparing(tag -> tag.tagName)) + .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof RecipeCard)) { + return false; + } + + // state check + RecipeCard card = (RecipeCard) other; + return id.getText().equals(card.id.getText()) + && recipe.equals(card.recipe); + } +} diff --git a/src/main/java/cookbuddy/ui/RecipeListPanel.java b/src/main/java/cookbuddy/ui/RecipeListPanel.java new file mode 100644 index 00000000000..088fc46453c --- /dev/null +++ b/src/main/java/cookbuddy/ui/RecipeListPanel.java @@ -0,0 +1,46 @@ +package cookbuddy.ui; + +import java.util.logging.Logger; + +import cookbuddy.commons.core.LogsCenter; + +import cookbuddy.model.recipe.Recipe; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; + +/** + * Panel containing the list of recipes. + */ +public class RecipeListPanel extends UiPart { + private static final String FXML = "RecipeListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(RecipeListPanel.class); + + @FXML + private ListView recipeListView; + + public RecipeListPanel(ObservableList recipeList) { + super(FXML); + recipeListView.setItems(recipeList); + recipeListView.setCellFactory(listView -> new RecipeListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Recipe} using a {@code PersonCard}. + */ + class RecipeListViewCell extends ListCell { + @Override + protected void updateItem(Recipe recipe, boolean empty) { + super.updateItem(recipe, empty); + + if (empty || recipe == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new RecipeCard(recipe, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/cookbuddy/ui/RecipeView.java b/src/main/java/cookbuddy/ui/RecipeView.java new file mode 100644 index 00000000000..7a4f2942663 --- /dev/null +++ b/src/main/java/cookbuddy/ui/RecipeView.java @@ -0,0 +1,48 @@ +package cookbuddy.ui; + +import cookbuddy.model.recipe.Recipe; +import cookbuddy.model.recipe.attribute.Ingredient; +import cookbuddy.model.recipe.attribute.Instruction; +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Region; + +/** + * RecipeView + */ +public class RecipeView extends UiPart { + + private static final String FXML = "RecipeView.fxml"; + private final Recipe recipe; + + @FXML + private Label name; + @FXML + private ImageView recipeImage; + @FXML + private ListView ingredients; + @FXML + private ListView instructions; + + + public RecipeView(Recipe recipe) { + super(FXML); + this.recipe = recipe; + + this.name.setText(recipe.getName().toString()); + this.ingredients.setItems(FXCollections.observableList(this.recipe.getIngredients().asList())); + this.instructions.setItems(FXCollections.observableList(this.recipe.getInstructions().asList())); + this.recipeImage.setImage(new Image(this.recipe.getPhotograph().getInputStream())); + this.ingredients.setStyle(".list-cell:empty {-fx-background-color: transparent;}"); + } + + public RecipeView() { + super(FXML); + this.recipe = null; + this.name.setText("Welcome to CookBuddy!"); + } +} diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/cookbuddy/ui/ResultDisplay.java similarity index 81% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/cookbuddy/ui/ResultDisplay.java index 7d98e84eedf..592a58b8485 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/cookbuddy/ui/ResultDisplay.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package cookbuddy.ui; import static java.util.Objects.requireNonNull; @@ -18,6 +18,7 @@ public class ResultDisplay extends UiPart { public ResultDisplay() { super(FXML); + resultDisplay.setStyle("-fx-font-family: Consolas, 'SF Mono', 'Menlo', 'Hack', 'Liberation Mono', 'monospace'"); } public void setFeedbackToUser(String feedbackToUser) { diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/cookbuddy/ui/StatusBarFooter.java similarity index 95% rename from src/main/java/seedu/address/ui/StatusBarFooter.java rename to src/main/java/cookbuddy/ui/StatusBarFooter.java index 7e17911323f..3c8e0941560 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/cookbuddy/ui/StatusBarFooter.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package cookbuddy.ui; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/cookbuddy/ui/Ui.java similarity index 86% rename from src/main/java/seedu/address/ui/Ui.java rename to src/main/java/cookbuddy/ui/Ui.java index 17aa0b494fe..b297e5139d3 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/cookbuddy/ui/Ui.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package cookbuddy.ui; import javafx.stage.Stage; diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/cookbuddy/ui/UiManager.java similarity index 67% rename from src/main/java/seedu/address/ui/UiManager.java rename to src/main/java/cookbuddy/ui/UiManager.java index 876621d79b9..386bec1ba47 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/cookbuddy/ui/UiManager.java @@ -1,16 +1,17 @@ -package seedu.address.ui; +package cookbuddy.ui; import java.util.logging.Logger; +import cookbuddy.MainApp; +import cookbuddy.commons.core.LogsCenter; +import cookbuddy.commons.util.StringUtil; +import cookbuddy.logic.Logic; +import cookbuddy.model.recipe.Recipe; import javafx.application.Platform; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.image.Image; import javafx.stage.Stage; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; /** * The manager of the UI component. @@ -19,17 +20,21 @@ public class UiManager implements Ui { public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane"; + private static Recipe viewedRecipe = null; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/recipe_book_32.png"; + private static String commandDescription = ""; - private Logic logic; - private MainWindow mainWindow; + + private static Logic logic; + private static MainWindow mainWindow; public UiManager(Logic logic) { super(); this.logic = logic; } + @Override public void start(Stage primaryStage) { logger.info("Starting UI..."); @@ -41,6 +46,9 @@ public void start(Stage primaryStage) { mainWindow = new MainWindow(primaryStage, logic); mainWindow.show(); //This should be called before creating other UI parts mainWindow.fillInnerParts(); + if (logic.getFilteredRecipeList().size() > 0) { + viewedRecipe = logic.getFilteredRecipeList().get(0); + } } catch (Throwable e) { logger.severe(StringUtil.getDetails(e)); @@ -48,6 +56,35 @@ public void start(Stage primaryStage) { } } + public static void setCommandDescription (String commandDesc) { + commandDescription = commandDesc; + } + + public static String getCommandDescription() { + return commandDescription; + } + + /** + * changes the displayed recipe + * @param e the new recipe to be displayed. + */ + public static void changeRecipe(Recipe e) { + mainWindow.defaultFill(e); + viewedRecipe = e; + } + + /** + * + * @return the recipe that is being displayed. + */ + public static Recipe getViewedRecipe() { + return viewedRecipe; + } + + public static void removeRecipe() { + mainWindow.fillInnerParts(); + } + private Image getImage(String imagePath) { return new Image(MainApp.class.getResourceAsStream(imagePath)); } diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/cookbuddy/ui/UiPart.java similarity index 97% rename from src/main/java/seedu/address/ui/UiPart.java rename to src/main/java/cookbuddy/ui/UiPart.java index fc820e01a9c..0f233eefc07 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/cookbuddy/ui/UiPart.java @@ -1,12 +1,12 @@ -package seedu.address.ui; +package cookbuddy.ui; import static java.util.Objects.requireNonNull; import java.io.IOException; import java.net.URL; +import cookbuddy.MainApp; import javafx.fxml.FXMLLoader; -import seedu.address.MainApp; /** * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc. diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java deleted file mode 100644 index 1deb3a1e469..00000000000 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ /dev/null @@ -1,13 +0,0 @@ -package seedu.address.commons.core; - -/** - * Container for user visible messages. - */ -public class Messages { - - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; - public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - -} diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/address/commons/util/FileUtil.java deleted file mode 100644 index b1e2767cdd9..00000000000 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ /dev/null @@ -1,83 +0,0 @@ -package seedu.address.commons.util; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; -import java.nio.file.Paths; - -/** - * Writes and reads files - */ -public class FileUtil { - - private static final String CHARSET = "UTF-8"; - - public static boolean isFileExists(Path file) { - return Files.exists(file) && Files.isRegularFile(file); - } - - /** - * Returns true if {@code path} can be converted into a {@code Path} via {@link Paths#get(String)}, - * otherwise returns false. - * @param path A string representing the file path. Cannot be null. - */ - public static boolean isValidPath(String path) { - try { - Paths.get(path); - } catch (InvalidPathException ipe) { - return false; - } - return true; - } - - /** - * Creates a file if it does not exist along with its missing parent directories. - * @throws IOException if the file or directory cannot be created. - */ - public static void createIfMissing(Path file) throws IOException { - if (!isFileExists(file)) { - createFile(file); - } - } - - /** - * Creates a file if it does not exist along with its missing parent directories. - */ - public static void createFile(Path file) throws IOException { - if (Files.exists(file)) { - return; - } - - createParentDirsOfFile(file); - - Files.createFile(file); - } - - /** - * Creates parent directories of file if it has a parent directory - */ - public static void createParentDirsOfFile(Path file) throws IOException { - Path parentDir = file.getParent(); - - if (parentDir != null) { - Files.createDirectories(parentDir); - } - } - - /** - * Assumes file exists - */ - public static String readFromFile(Path file) throws IOException { - return new String(Files.readAllBytes(file), CHARSET); - } - - /** - * Writes given string to a file. - * Will create the file if it does not exist yet. - */ - public static void writeToFile(Path file, String content) throws IOException { - Files.write(file, content.getBytes(CHARSET)); - } - -} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index 71656d7c5c8..00000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -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_TAG; - -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -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 + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; - - 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; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java deleted file mode 100644 index 9c86b1fa6e4..00000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,23 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.model.AddressBook; -import seedu.address.model.Model; - -/** - * Clears the address book. - */ -public class ClearCommand extends Command { - - public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.setAddressBook(new AddressBook()); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index 7e36114902f..00000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,226 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -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_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -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; - -/** - * Edits the details of an existing person in the address book. - */ -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 " - + "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) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %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."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - // state check - EditCommand e = (EditCommand) other; - return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional

getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; - - return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 84be6ad2596..00000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.model.Model; - -/** - * Lists all persons in the address book to the user. - */ -public class ListCommand extends Command { - - public static final String COMMAND_WORD = "list"; - - 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); - } -} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 3b8bfa035e8..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.parser; - -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_EMAIL; -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_TAG; - -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.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; - -/** - * Parses input arguments and creates a new AddCommand object - */ -public class AddCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. - * @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); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !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()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } - - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java deleted file mode 100644 index 1e466792b46..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,76 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -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.parser.exceptions.ParseException; - -/** - * Parses user input. - */ -public class AddressBookParser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - * @throws ParseException if the user input does not conform the expected format - */ - public Command parseCommand(String userInput) throws ParseException { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - default: - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java deleted file mode 100644 index 75b1a9bf119..00000000000 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ /dev/null @@ -1,15 +0,0 @@ -package seedu.address.logic.parser; - -/** - * Contains Command Line Interface (CLI) syntax definitions common to multiple commands - */ -public class CliSyntax { - - /* Prefix definitions */ - public static final Prefix PREFIX_NAME = new Prefix("n/"); - 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/"); - -} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java deleted file mode 100644 index 845644b7dea..00000000000 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ /dev/null @@ -1,82 +0,0 @@ -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_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -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_TAG; - -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -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; - -/** - * Parses input arguments and creates a new EditCommand object - */ -public class EditCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. - * @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); - - Index index; - - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); - } - - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); - } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); - } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); - } - - return new EditCommand(index, editPersonDescriptor); - } - - /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. - */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index 4fb71f23103..00000000000 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import java.util.Arrays; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Parses input arguments and creates a new FindCommand object - */ -public class FindCommandParser 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 FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - String[] nameKeywords = trimmedArgs.split("\\s+"); - - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java deleted file mode 100644 index b117acb9c55..00000000000 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ /dev/null @@ -1,124 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Contains utility methods used for parsing strings in the various *Parser classes. - */ -public class ParserUtil { - - public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; - - /** - * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be - * trimmed. - * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). - */ - public static Index parseIndex(String oneBasedIndex) throws ParseException { - String trimmedIndex = oneBasedIndex.trim(); - if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { - throw new ParseException(MESSAGE_INVALID_INDEX); - } - return Index.fromOneBased(Integer.parseInt(trimmedIndex)); - } - - /** - * Parses a {@code String name} into a {@code Name}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code name} is invalid. - */ - public static Name parseName(String name) throws ParseException { - requireNonNull(name); - String trimmedName = name.trim(); - if (!Name.isValidName(trimmedName)) { - throw new ParseException(Name.MESSAGE_CONSTRAINTS); - } - return new Name(trimmedName); - } - - /** - * Parses a {@code String phone} into a {@code Phone}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code phone} is invalid. - */ - public static Phone parsePhone(String phone) throws ParseException { - requireNonNull(phone); - String trimmedPhone = phone.trim(); - if (!Phone.isValidPhone(trimmedPhone)) { - throw new ParseException(Phone.MESSAGE_CONSTRAINTS); - } - return new Phone(trimmedPhone); - } - - /** - * Parses a {@code String address} into an {@code Address}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code address} is invalid. - */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_CONSTRAINTS); - } - return new Address(trimmedAddress); - } - - /** - * Parses a {@code String email} into an {@code Email}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code email} is invalid. - */ - public static Email parseEmail(String email) throws ParseException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new ParseException(Email.MESSAGE_CONSTRAINTS); - } - return new Email(trimmedEmail); - } - - /** - * Parses a {@code String tag} into a {@code Tag}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code tag} is invalid. - */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(trimmedTag); - } - - /** - * Parses {@code Collection tags} into a {@code Set}. - */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); - } - return tagSet; - } -} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 1a943a0781a..00000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - - /* - * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html - * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. - */ - { - persons = new UniquePersonList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - this.persons.setPersons(persons); - } - - /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. - */ - public void resetData(ReadOnlyAddressBook newData) { - requireNonNull(newData); - - setPersons(newData.getPersonList()); - } - - //// person-level operations - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); - } - - /** - * Adds a person to the address book. - * The person must not already exist in the address book. - */ - public void addPerson(Person p) { - persons.add(p); - } - - /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); - - persons.setPerson(target, editedPerson); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. - */ - public void removePerson(Person key) { - persons.remove(key); - } - - //// util methods - - @Override - public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; - // TODO: refine later - } - - @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); - } - - @Override - public int hashCode() { - return persons.hashCode(); - } -} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java deleted file mode 100644 index d54df471c1f..00000000000 --- a/src/main/java/seedu/address/model/Model.java +++ /dev/null @@ -1,87 +0,0 @@ -package seedu.address.model; - -import java.nio.file.Path; -import java.util.function.Predicate; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; - -/** - * The API of the Model component. - */ -public interface Model { - /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; - - /** - * Replaces user prefs data with the data in {@code userPrefs}. - */ - void setUserPrefs(ReadOnlyUserPrefs userPrefs); - - /** - * Returns the user prefs. - */ - ReadOnlyUserPrefs getUserPrefs(); - - /** - * Returns the user prefs' GUI settings. - */ - GuiSettings getGuiSettings(); - - /** - * Sets the user prefs' GUI settings. - */ - void setGuiSettings(GuiSettings guiSettings); - - /** - * Returns the user prefs' address book file path. - */ - Path getAddressBookFilePath(); - - /** - * Sets the user prefs' address book file path. - */ - void setAddressBookFilePath(Path addressBookFilePath); - - /** - * Replaces address book data with the data in {@code addressBook}. - */ - void setAddressBook(ReadOnlyAddressBook addressBook); - - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - boolean hasPerson(Person person); - - /** - * Deletes the given person. - * The person must exist in the address book. - */ - void deletePerson(Person target); - - /** - * Adds the given person. - * {@code person} must not already exist in the address book. - */ - void addPerson(Person person); - - /** - * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - void setPerson(Person target, Person editedPerson); - - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); - - /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. - * @throws NullPointerException if {@code predicate} is null. - */ - void updateFilteredPersonList(Predicate predicate); -} diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java deleted file mode 100644 index 0650c954f5c..00000000000 --- a/src/main/java/seedu/address/model/ModelManager.java +++ /dev/null @@ -1,151 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.nio.file.Path; -import java.util.function.Predicate; -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.collections.transformation.FilteredList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Represents the in-memory model of the address book data. - */ -public class ModelManager implements Model { - private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - - private final AddressBook addressBook; - private final UserPrefs userPrefs; - private final FilteredList filteredPersons; - - /** - * Initializes a ModelManager with the given addressBook and userPrefs. - */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { - super(); - requireAllNonNull(addressBook, userPrefs); - - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); - - this.addressBook = new AddressBook(addressBook); - this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); - } - - public ModelManager() { - this(new AddressBook(), new UserPrefs()); - } - - //=========== UserPrefs ================================================================================== - - @Override - public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { - requireNonNull(userPrefs); - this.userPrefs.resetData(userPrefs); - } - - @Override - public ReadOnlyUserPrefs getUserPrefs() { - return userPrefs; - } - - @Override - public GuiSettings getGuiSettings() { - return userPrefs.getGuiSettings(); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - requireNonNull(guiSettings); - userPrefs.setGuiSettings(guiSettings); - } - - @Override - public Path getAddressBookFilePath() { - return userPrefs.getAddressBookFilePath(); - } - - @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - userPrefs.setAddressBookFilePath(addressBookFilePath); - } - - //=========== AddressBook ================================================================================ - - @Override - public void setAddressBook(ReadOnlyAddressBook addressBook) { - this.addressBook.resetData(addressBook); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; - } - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return addressBook.hasPerson(person); - } - - @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); - } - - @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - } - - @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - addressBook.setPerson(target, editedPerson); - } - - //=========== Filtered Person List Accessors ============================================================= - - /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} - */ - @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; - } - - @Override - public void updateFilteredPersonList(Predicate predicate) { - requireNonNull(predicate); - filteredPersons.setPredicate(predicate); - } - - @Override - public boolean equals(Object obj) { - // short circuit if same object - if (obj == this) { - return true; - } - - // instanceof handles nulls - if (!(obj instanceof ModelManager)) { - return false; - } - - // state check - ModelManager other = (ModelManager) obj; - return addressBook.equals(other.addressBook) - && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); - } - -} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java deleted file mode 100644 index 6ddc2cd9a29..00000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,17 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. - */ - ObservableList getPersonList(); - -} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index 60472ca22a0..00000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,57 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; - - /* - * 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 final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(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 Address // instanceof handles nulls - && value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java deleted file mode 100644 index a5bbe0b6a5f..00000000000 --- a/src/main/java/seedu/address/model/person/Email.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's email in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} - */ -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 + ") .\n" - + "2. This is followed by a '@' and then a domain name. " - + "The domain name must:\n" - + " - be at least 2 characters long\n" - + " - start and end with alphanumeric characters\n" - + " - consist of alphanumeric characters, a period or a hyphen for the characters in between, if any."; - // alphanumeric and special characters - private static final String LOCAL_PART_REGEX = "^[\\w" + SPECIAL_CHARACTERS + "]+"; - private static final String DOMAIN_FIRST_CHARACTER_REGEX = "[^\\W_]"; // alphanumeric characters except underscore - private static final String DOMAIN_MIDDLE_REGEX = "[a-zA-Z0-9.-]*"; // alphanumeric, period and hyphen - private static final String DOMAIN_LAST_CHARACTER_REGEX = "[^\\W_]$"; - public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" - + DOMAIN_FIRST_CHARACTER_REGEX + DOMAIN_MIDDLE_REGEX + DOMAIN_LAST_CHARACTER_REGEX; - - public final String value; - - /** - * Constructs an {@code Email}. - * - * @param email A valid email address. - */ - public Email(String email) { - requireNonNull(email); - checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS); - value = email; - } - - /** - * Returns if a given string is a valid email. - */ - public static boolean isValidEmail(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 Email // instanceof handles nulls - && value.equals(((Email) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 557a7a60cd5..00000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - - /** - * Returns true if both persons of the same name have at least one other identity field that is the same. - * This defines a weaker notion of equality between two persons. - */ - public boolean isSamePerson(Person otherPerson) { - if (otherPerson == this) { - return true; - } - - return otherPerson != null - && otherPerson.getName().equals(getName()) - && (otherPerson.getPhone().equals(getPhone()) || otherPerson.getEmail().equals(getEmail())); - } - - /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof Person)) { - return false; - } - - 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()); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append(" Phone: ") - .append(getPhone()) - .append(" Email: ") - .append(getEmail()) - .append(" Address: ") - .append(getAddress()) - .append(" Tags: "); - getTags().forEach(builder::append); - return builder.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java deleted file mode 100644 index 872c76b382f..00000000000 --- a/src/main/java/seedu/address/model/person/Phone.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's phone number in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} - */ -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 final String value; - - /** - * Constructs a {@code Phone}. - * - * @param phone A valid phone number. - */ - public Phone(String phone) { - requireNonNull(phone); - checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS); - value = phone; - } - - /** - * Returns true if a given string is a valid phone number. - */ - public static boolean isValidPhone(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 Phone // instanceof handles nulls - && value.equals(((Phone) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index 0fee4fe57e6..00000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,137 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Iterator; -import java.util.List; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so - * as to ensure that the person with exactly the same fields will be removed. - * - * Supports a minimal set of list operations. - * - * @see Person#isSamePerson(Person) - */ -public class UniquePersonList implements Iterable { - - private final ObservableList internalList = FXCollections.observableArrayList(); - private final ObservableList internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); - } - - /** - * Adds a person to the list. - * The person must not already exist in the list. - */ - public void add(Person toAdd) { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * The person must exist in the list. - */ - public void remove(Person toRemove) { - requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); - } - } - - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - - /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return internalUnmodifiableList; - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - /** - * Returns true if {@code persons} contains only unique persons. - */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java deleted file mode 100644 index d7290f59442..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,11 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same - * identity). - */ -public class DuplicatePersonException extends RuntimeException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index fa764426ca7..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends RuntimeException {} 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/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index 4599182b3f9..00000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - Path getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional readAddressBook() throws DataConversionException, IOException; - - /** - * @see #getAddressBookFilePath() - */ - Optional readAddressBook(Path filePath) throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java deleted file mode 100644 index a6321cec2ea..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -import seedu.address.commons.exceptions.IllegalValueException; -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; - -/** - * Jackson-friendly version of {@link Person}. - */ -class JsonAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - private final String name; - private final String phone; - private final String email; - private final String address; - private final List tagged = 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) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged.addAll(tagged); - } - } - - /** - * Converts a given {@code Person} into this class for Jackson 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() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList())); - } - - /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. - * - * @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()); - } - - if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); - } - if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); - } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java deleted file mode 100644 index dfab9daaa0d..00000000000 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ /dev/null @@ -1,80 +0,0 @@ -package seedu.address.storage; - -import static java.util.Objects.requireNonNull; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * A class to access AddressBook data stored as a json file on the hard disk. - */ -public class JsonAddressBookStorage implements AddressBookStorage { - - private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class); - - private Path filePath; - - public JsonAddressBookStorage(Path filePath) { - this.filePath = filePath; - } - - public Path getAddressBookFilePath() { - return filePath; - } - - @Override - public Optional readAddressBook() throws DataConversionException { - return readAddressBook(filePath); - } - - /** - * Similar to {@link #readAddressBook()}. - * - * @param filePath location of the data. Cannot be null. - * @throws DataConversionException if the file is not in the correct format. - */ - public Optional readAddressBook(Path filePath) throws DataConversionException { - requireNonNull(filePath); - - Optional jsonAddressBook = JsonUtil.readJsonFile( - filePath, JsonSerializableAddressBook.class); - if (!jsonAddressBook.isPresent()) { - return Optional.empty(); - } - - try { - return Optional.of(jsonAddressBook.get().toModelType()); - } catch (IllegalValueException ive) { - logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); - throw new DataConversionException(ive); - } - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); - } - - /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}. - * - * @param filePath location of the data. Cannot be null. - */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - requireNonNull(addressBook); - requireNonNull(filePath); - - FileUtil.createIfMissing(filePath); - JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java deleted file mode 100644 index 5efd834091d..00000000000 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonRootName; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; - -/** - * An Immutable AddressBook that is serializable to JSON format. - */ -@JsonRootName(value = "addressbook") -class JsonSerializableAddressBook { - - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; - - private final List persons = new ArrayList<>(); - - /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. - */ - @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); - } - - /** - * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. - * - * @param source future changes to this will not affect the created {@code JsonSerializableAddressBook}. - */ - public JsonSerializableAddressBook(ReadOnlyAddressBook source) { - persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList())); - } - - /** - * Converts this address book into the model's {@code AddressBook} object. - * - * @throws IllegalValueException if there were any data constraints violated. - */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); - for (JsonAdaptedPerson jsonAdaptedPerson : persons) { - Person person = jsonAdaptedPerson.toModelType(); - if (addressBook.hasPerson(person)) { - throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); - } - addressBook.addPerson(person); - } - return addressBook; - } - -} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java deleted file mode 100644 index beda8bd9f11..00000000000 --- a/src/main/java/seedu/address/storage/Storage.java +++ /dev/null @@ -1,32 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; - -/** - * API of the Storage component - */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { - - @Override - Optional readUserPrefs() throws DataConversionException, IOException; - - @Override - void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; - - @Override - Path getAddressBookFilePath(); - - @Override - Optional readAddressBook() throws DataConversionException, IOException; - - @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java deleted file mode 100644 index e4f452b6cbf..00000000000 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ /dev/null @@ -1,77 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; - -/** - * Manages storage of AddressBook data in local storage. - */ -public class StorageManager implements Storage { - - private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; - private UserPrefsStorage userPrefsStorage; - - - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { - super(); - this.addressBookStorage = addressBookStorage; - this.userPrefsStorage = userPrefsStorage; - } - - // ================ UserPrefs methods ============================== - - @Override - public Path getUserPrefsFilePath() { - return userPrefsStorage.getUserPrefsFilePath(); - } - - @Override - public Optional readUserPrefs() throws DataConversionException, IOException { - return userPrefsStorage.readUserPrefs(); - } - - @Override - public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { - userPrefsStorage.saveUserPrefs(userPrefs); - } - - - // ================ AddressBook methods ============================== - - @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); - } - - @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); - } - - @Override - public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { - logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); - } - -} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java deleted file mode 100644 index 7d76e691f52..00000000000 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ /dev/null @@ -1,77 +0,0 @@ -package seedu.address.ui; - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.TextField; -import javafx.scene.layout.Region; -import seedu.address.logic.commands.CommandResult; -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. - */ -public class CommandBox extends UiPart { - - public static final String ERROR_STYLE_CLASS = "error"; - private static final String FXML = "CommandBox.fxml"; - - private final CommandExecutor commandExecutor; - - @FXML - private TextField commandTextField; - - 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()); - } - - /** - * Handles the Enter button pressed event. - */ - @FXML - private void handleCommandEntered() { - try { - commandExecutor.execute(commandTextField.getText()); - commandTextField.setText(""); - } catch (CommandException | ParseException e) { - setStyleToIndicateCommandFailure(); - } - } - - /** - * Sets the command box style to use the default style. - */ - private void setStyleToDefault() { - commandTextField.getStyleClass().remove(ERROR_STYLE_CLASS); - } - - /** - * Sets the command box style to indicate a failed command. - */ - private void setStyleToIndicateCommandFailure() { - ObservableList styleClass = commandTextField.getStyleClass(); - - if (styleClass.contains(ERROR_STYLE_CLASS)) { - return; - } - - styleClass.add(ERROR_STYLE_CLASS); - } - - /** - * Represents a function that can execute commands. - */ - @FunctionalInterface - public interface CommandExecutor { - /** - * Executes the command and returns the result. - * - * @see seedu.address.logic.Logic#execute(String) - */ - CommandResult execute(String commandText) throws CommandException, ParseException; - } - -} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index 0684b088868..00000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,74 +0,0 @@ -package seedu.address.ui; - -import java.util.Comparator; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import seedu.address.model.person.Person; - -/** - * An UI component that displays information of a {@code Person}. - */ -public class PersonCard extends UiPart { - - private static final String FXML = "PersonListCard.fxml"; - - /** - * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. - * As a consequence, UI elements' variable names cannot be set to such keywords - * or an exception will be thrown by JavaFX during runtime. - * - * @see The issue on AddressBook level 4 - */ - - public final Person person; - - @FXML - private HBox cardPane; - @FXML - private Label name; - @FXML - private Label id; - @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private FlowPane tags; - - 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); - person.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof PersonCard)) { - return false; - } - - // state check - PersonCard card = (PersonCard) other; - return id.getText().equals(card.id.getText()) - && person.equals(card.person); - } -} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index 1328917096e..00000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,46 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView personListView; - - public PersonListPanel(ObservableList personList) { - super(FXML); - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png deleted file mode 100644 index 29810cf1fd9..00000000000 Binary files a/src/main/resources/images/address_book_32.png and /dev/null differ diff --git a/src/main/resources/images/hamsandwich_recipe.jpg b/src/main/resources/images/hamsandwich_recipe.jpg new file mode 100644 index 00000000000..32a68a35a88 Binary files /dev/null and b/src/main/resources/images/hamsandwich_recipe.jpg differ diff --git a/src/main/resources/images/idiotsandwich_recipe.jpg b/src/main/resources/images/idiotsandwich_recipe.jpg new file mode 100644 index 00000000000..d0989be75e2 Binary files /dev/null and b/src/main/resources/images/idiotsandwich_recipe.jpg differ diff --git a/src/main/resources/images/recipe_book_32.png b/src/main/resources/images/recipe_book_32.png new file mode 100644 index 00000000000..a155820b03e Binary files /dev/null and b/src/main/resources/images/recipe_book_32.png differ diff --git a/src/main/resources/images/recipe_placeholder.jpg b/src/main/resources/images/recipe_placeholder.jpg new file mode 100644 index 00000000000..d8ec0dbde4c Binary files /dev/null and b/src/main/resources/images/recipe_placeholder.jpg differ diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 09f6d6fe9e4..95c6bda6d45 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -1,9 +1,6 @@ - - + - - - +