Skip to content
Snippets Groups Projects
Commit 7058ff0c authored by Harry Linrui Xu's avatar Harry Linrui Xu
Browse files

Merge branch 'HS_Frontend_Recipes' into 'master'

Hs frontend recipes

See merge request !42
parents e2523052 1c9d7d12
No related branches found
No related tags found
1 merge request!42Hs frontend recipes
Pipeline #217861 passed
Showing
with 1546 additions and 104 deletions
package no.ntnu.idatt1002.demo.controller;
import javafx.animation.FadeTransition;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.event.ActionEvent;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.util.Duration;
import no.ntnu.idatt1002.demo.data.recipes.FileHandler;
import no.ntnu.idatt1002.demo.data.recipes.FoodItem;
import no.ntnu.idatt1002.demo.data.recipes.IngredientsAtHand;
public class AddIngredientController implements Initializable {
private ObservableList<String> ingredients;
private String[] ingredientsList;
@FXML
private Button addBtn;
@FXML
private ListView<String> listView;
@FXML
private TextField searchBar;
@FXML
private Button searchBtn;
@FXML
private Label status;
private String statusText = "Added: ";
@FXML
void addToFridge(ActionEvent event) throws IOException {
IngredientsAtHand ingredientsAtHand = FileHandler.readIngredientsAtHand("Fridge");
FoodItem item = FoodItem.valueOf(listView.getSelectionModel().getSelectedItem().replace(" ", "_").toUpperCase());
assert ingredientsAtHand != null;
if(!ingredientsAtHand.atHand(item)) {
ingredientsAtHand.addIngredient(item);
FileHandler.writeIngredientsAtHand(ingredientsAtHand, "Fridge");
if(status.isVisible() && status.getText().isBlank()) {
statusText += String.format("%s", item.label);
} else if (status.isVisible()){
statusText += String.format(", %s", item.label);
}
status.setText(statusText); // Only if not already in list!!
}
}
@FXML
void search() {
listView.getItems().clear();
listView.getItems().addAll(searchList(searchBar.getText(),
Arrays.stream(FoodItem.values()).toList().stream().map(value -> value.label).toArray(String[]::new))); // String[]
}
private List<String> searchList(String searchWords, String[] listOfStrings) {
String[] searchWordsArray = searchWords.trim().split(" ");
return Arrays.stream(listOfStrings).filter((in) -> {
return Arrays.stream(searchWordsArray).allMatch((word) ->
in.toLowerCase().contains(word.toLowerCase()));
}).collect(Collectors.toList());
}
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
listView.setItems(FXCollections.observableArrayList(Arrays.stream(FoodItem.values()).map(value -> value.label).toList()));
Platform.runLater(() -> searchBar.requestFocus());
status.setWrapText(true);
}
}
package no.ntnu.idatt1002.demo.controller;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import no.ntnu.idatt1002.demo.data.recipes.*;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
public class AllRecipesController implements Initializable {
IngredientsAtHand ingredientsAtHand;
RecipeRegister recipeRegister;
@FXML
private Button goBackBtn;
@FXML
private ListView<String> allList;
private ObservableList<String> recipes;
private String selectedRecipeName;
@FXML
private void goBack(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/view/SuggestRecipes.fxml"));
Parent root = loader.load();
Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
private void showRecipe(String recipeName) throws IOException {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/view/Recipe.fxml"));
Recipe recipeOfInterest = recipeRegister.getRecipe(recipeName);
Parent root = loader.load();
RecipeController recipeController = loader.getController();
recipeController.setData(recipeOfInterest);
Stage stage = (Stage)allList.getParent().getScene().getWindow();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
private float percent(int a, int b) {
if(b != 0 && a != 0) {
return (float) a / b;
} else {
return 0;
}
}
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
ingredientsAtHand = FileHandler.readIngredientsAtHand("Fridge");
recipeRegister = FileHandler.readRecipeRegister("Recipes");
int numberOfRecipes = recipeRegister.getRecipes().size();
ArrayList<Recipe> sortedRecipes = recipeRegister.pickBestFits(numberOfRecipes, ingredientsAtHand);
recipes = FXCollections.observableArrayList(sortedRecipes.stream().map(recipe -> {
return String.format("# %s - %d missing ingredients (%.2f %%)", recipe.getName(), recipe.getMissingIngredients(), percent(recipe.getIngredientList().size() - recipe.getMissingIngredients(),recipe.getIngredientList().size()));
}).toList());
allList.setItems(recipes);
allList.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
selectedRecipeName = allList.getSelectionModel()
.getSelectedItem().split("-|#")[1].strip();
try {
showRecipe(selectedRecipeName);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
}
package no.ntnu.idatt1002.demo.controller;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import no.ntnu.idatt1002.demo.data.recipes.RecipeIngredient;
import java.net.URL;
import java.util.ResourceBundle;
public class IngredientTileController implements Initializable {
@FXML
private Label text;
@FXML
private Pane ingredientPane;
public void setData(RecipeIngredient ingredient) {
StringBuilder sb = new StringBuilder();
sb.append("# ").append(ingredient.getFoodType().label.substring(0,1).toUpperCase())
.append(ingredient.getFoodType().label.substring(1));
sb.append(" ").append(ingredient.getAmount()).append(" ").append(ingredient.getUnit().label);
text.setText(String.valueOf(sb));
}
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
}
}
...@@ -236,7 +236,7 @@ public class MainMenu { ...@@ -236,7 +236,7 @@ public class MainMenu {
private void switchScene(ActionEvent event) throws IOException { private void switchScene(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader(); FXMLLoader loader = new FXMLLoader();
if (event.getSource() == foodBtn) { if (event.getSource() == foodBtn) {
System.out.println("Food button pressed"); loader.setLocation(getClass().getResource("/view/SuggestRecipes.fxml"));
} else if (event.getSource() == expenseBtn) { } else if (event.getSource() == expenseBtn) {
loader.setLocation(getClass().getResource("/view/IncomeAndExpenses.fxml")); loader.setLocation(getClass().getResource("/view/IncomeAndExpenses.fxml"));
} else if (event.getSource() == incomeBtn) { } else if (event.getSource() == incomeBtn) {
......
package no.ntnu.idatt1002.demo.controller;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import no.ntnu.idatt1002.demo.data.recipes.*;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
public class RecipeController implements Initializable {
@FXML
private Label recipeName;
@FXML
private Text instructions;
@FXML
private Button goBackBtn;
@FXML
private VBox ingredientList;
@FXML
private ObservableList<RecipeIngredient> ingredients;
@FXML
private Pane ingredientPane;
@FXML
private Button allRecipesBtn;
private Recipe recipe;
public void setData(Recipe recipeOfInterest) {
recipe = recipeOfInterest;
recipeName.setText(recipe.getName());
instructions.setText(recipe.getInstructions());
ingredients = FXCollections.observableArrayList(recipe.getIngredientList());
setIngredientTiles();
}
private void setIngredientTiles() {
for(RecipeIngredient ri : ingredients) {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/IngredientTile.fxml"));
try {
Pane pane = loader.load();
IngredientTileController ingredientTileController = loader.getController(); //Todo: is null
ingredientTileController.setData(ri);
ingredientList.getChildren().add(pane);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@FXML
private void goBack(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/view/SuggestRecipes.fxml"));
Parent root = loader.load();
Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
@FXML
private void toAllRecipes(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/view/AllRecipes.fxml"));
Parent root = loader.load();
Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// ingredients = FXCollections.observableArrayList(recipe.getIngredientList());
}
}
package no.ntnu.idatt1002.demo.controller;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import no.ntnu.idatt1002.demo.data.recipes.FileHandler;
import no.ntnu.idatt1002.demo.data.recipes.Recipe;
import no.ntnu.idatt1002.demo.data.recipes.RecipeRegister;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
public class RecipeTileController implements Initializable {
@FXML
private Button nameTag;
@FXML
private Label missingTag;
@FXML
private VBox recipeTile;
private RecipeRegister recipeRegister;
@FXML
private void tileClick(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/view/Recipe.fxml"));
String recipeName = this.nameTag.getText();
Recipe recipeOfInterest = recipeRegister.getRecipe(recipeName);
Parent root = loader.load();
RecipeController recipeController = loader.getController();
recipeController.setData(recipeOfInterest);
Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public void setData(Recipe recipe) {
nameTag.setText(recipe.getName());
missingTag.setText(Integer.toString(recipe.getMissingIngredients()));
}
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
recipeRegister = FileHandler.readRecipeRegister("Recipes");
nameTag.setWrapText(true);
}
}
package no.ntnu.idatt1002.demo.controller;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import no.ntnu.idatt1002.demo.data.recipes.*;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Optional;
import java.util.ResourceBundle;
public class SuggestRecipesController implements Initializable {
IngredientsAtHand ingredientsAtHand;
RecipeRegister recipeRegister;
@FXML
private Button addToFridgeBtn;
@FXML
private Button removeBtn;
@FXML
private Button showAllBtn;
@FXML
private Button goBackBtn;
@FXML
private ListView<String> fridgeList;
@FXML
private GridPane recipeGrid;
@FXML
private Label missingList;
@FXML
private VBox recipeTile;
private ObservableList<String> fridge;
private ObservableList<Recipe> recipes;
private final int NUMBER_OF_TILES = 4;
private final ArrayList<VBox> currentRecipeTiles = new ArrayList<>(4);
@FXML
private void addIngredient(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/view/AddIngredient.fxml"));
DialogPane addIngredientPane = loader.load();
Dialog<ButtonType> dialog = new Dialog<>();
dialog.setDialogPane(addIngredientPane);
dialog.setTitle("Add ingredient to fridge");
Optional<ButtonType> clickedButton = dialog.showAndWait();
if (clickedButton.isPresent() && clickedButton.get() == ButtonType.CLOSE) {
// Refresh ingredientsAtHand.
readIngredientsAtHand();
setRecipeTiles();
}
}
@FXML
private void removeFromFridge(ActionEvent event) throws IOException {
String toRemove = fridgeList.getSelectionModel().getSelectedItem();
//TODO: If anything selected!
if(toRemove != null) {
ingredientsAtHand.removeIngredient(FoodItem.valueOf(toRemove.replace(" ", "_").toUpperCase()));
storeIngredientsAtHand();
setRecipeTiles();
}
}
private void setRecipeTiles() {
// Ingredeints at hand and recipesRegister
ArrayList<Recipe> recipes = recipeRegister.pickBestFits(NUMBER_OF_TILES, ingredientsAtHand);
int i = 0;
int j = 0;
int counter = 0;
for (Recipe r : recipes) {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/view/RecipeTile.fxml"));
if (i > 1) {
j++;
i = 0;
}
try {
VBox vBox = loader.load();
RecipeTileController recipeTileController = loader.getController();
recipeTileController.setData(r);
if (currentRecipeTiles.size() < recipes.size()) {
currentRecipeTiles.add(vBox);
} else {
recipeGrid.getChildren().remove(currentRecipeTiles.get(counter));
currentRecipeTiles.set(counter, vBox);
}
setHoverEffect(vBox, r);
recipeGrid.add(vBox, i, j);
} catch (IOException e) {
throw new RuntimeException(e);
}
i++;
counter++;
}
}
private void setHoverEffect(VBox vBox, Recipe recipe) {
vBox.setOnMouseEntered(event -> {
if(recipe.getMissingIngredients()==0) {
missingList.setText("");
missingList.setVisible(false);
} else {
missingList.setText("Missing: " + String.join(", ", recipe.getMissingList()));
missingList.setVisible(true);
}
});
vBox.setOnMouseExited(event -> {
missingList.setText("");
missingList.setVisible(false);
});
}
@FXML
private void switchScene(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader();
if (event.getSource() == showAllBtn) {
loader.setLocation(getClass().getResource("/view/AllRecipes.fxml"));
} else if (event.getSource() == goBackBtn) {
loader.setLocation(getClass().getResource("/view/MainMenuNew.fxml"));
}
Parent root = loader.load();
Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setResizable(false);
stage.show();
}
public void readIngredientsAtHand() {
// If no ingredients at hand file exsists, add one and let it be empty. //TODO
ingredientsAtHand = FileHandler.readIngredientsAtHand("Fridge");
fridge = FXCollections.observableArrayList(ingredientsAtHand.getIngredientsAtHand().stream().map(foodItem -> foodItem.label).toList());
//List<String> fridgeLabels = fridge;
fridgeList.setItems(fridge.sorted());
}
public void storeIngredientsAtHand() throws IOException {
FileHandler.writeIngredientsAtHand(ingredientsAtHand, "Fridge");
fridge = FXCollections.observableArrayList(ingredientsAtHand.getIngredientsAtHand().stream().map(foodItem -> foodItem.label).toList());
fridgeList.setItems(fridge.sorted());
}
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
readIngredientsAtHand();
recipeRegister = FileHandler.readRecipeRegister("Recipes");
recipes = FXCollections.observableArrayList(recipeRegister.getRecipes());
missingList.setVisible(false);
// Get the number from FX-grid available?
setRecipeTiles();
}
}
package no.ntnu.idatt1002.demo.data.recipes;
import java.io.*;
import java.util.List;
import java.util.Scanner;
/**
* The FileHandler class is a static class that handles reading and writing to the .register files for storing
* dinner recipes and ingredients at hand. Files of this class are stored at src/main/resources/recipes.
*
* @author hannesofie
*/
public class FileHandler {
private static final String fileType = ".register";
private static final String filePath = "src/main/resources/recipes/";
/**
* The method takes a RecipeRegister object and a String as parameters. The recipe register is then written
* to a file named after the provided string, with the file-type ".register" in the /main/recourses/recipes folder.
* The file is written at the following format and is only sensitive to new-lines within the instructions-String:
* # Recipe name
* - Ingredient 1 | amount | unit
* - Ingredient 2 | amount | unit
* - ...
* Instructions
* An IllegalArgumentException is thrown if the recipe register is null. IOExceptions may occur upon writing to
* file, in which case the stacktrace is printed to terminal.
*
* @param recipeRegister A recipe register object that is to be written to file.
* @param title The title by which to name the .register-file.
*/
public static void writeRegister(RecipeRegister recipeRegister, String title) {
if (recipeRegister == null) {
throw new IllegalArgumentException("Only a valid register object can be written to file.");
}
try (FileWriter fileWriter = new FileWriter(filePath + title + fileType)) {
recipeRegister.getRecipes().forEach((recipe) ->
{
try {
fileWriter.write(formatRecipe(recipe).toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* The method supports the method for writing a recipe register to file by taking in a single recipe object
* and return it as a String at the correct format for storage.
*
* @param recipe A recipe object to format into a String.
* @return A String representation of the recipe at the specified format for file storage.
*/
public static StringBuilder formatRecipe(Recipe recipe) {
StringBuilder sb = new StringBuilder();
sb.append("# ")
.append(recipe.getName())
.append("\n")
.append(formatIngredientList(recipe.getIngredientList()))
.append("\n")
.append(recipe.getInstructions())
.append("\n\n");
return sb;
}
/**
* The method supports the 'formatRecipe' method by receiving a list of ingredient objects and returning a
* String at the correct format for the writing to file.
*
* @param ingredientList A list of ingredients to be formatted into a String.
* @return A String of the ingredients at the correct format for writing to file.
*/
public static StringBuilder formatIngredientList(List<RecipeIngredient> ingredientList) {
StringBuilder sb = new StringBuilder();
ingredientList.forEach((ingredient) -> sb.append("- ")
.append(ingredient.getFoodType())
.append(" | ")
.append(ingredient.getAmount())
.append(" | ")
.append(ingredient.getUnit())
.append("\n"));
return sb;
}
/**
* The method reads a recipe register from file and returns the recipe register object. The title of the file
* is provided as a String. If the file doesn't exist, a message is written to the terminal and null is returned
* instead of a recipe register. Each recipe is separated by a '#'-sign and passed on to the method 'readRecipe'
* that reads and returns each Recipe object for the register.
*
* @param title Title of the .register file at which the recipe register is saved.
* @return A recipe register object read from file.
*/
public static RecipeRegister readRecipeRegister(String title) {
File file = new File(filePath + title + fileType);
RecipeRegister register = new RecipeRegister();
try (Scanner sc = new Scanner(file)) {
sc.useDelimiter("#");
String line;
while (sc.hasNext()) {
line = sc.next();
if (!line.isBlank()) {
register.addRecipe(readRecipe(line));
}
}
} catch (FileNotFoundException e) {
System.out.println("The file was not found.");
return null;
}
return register;
}
/**
* The method supports the readRecipeRegister method by receiving a string containing the information needed to
* create one specific recipe object. The method reads the needed information from this String and returns
* the Recipe object based on this. The beginning of each ingredient line is recognized by a hyphen ('-'), while
* the instructions are all lines, including internal new-lines, that do not start with a hyphen.
*
* @param readRecipe A String representation of one recipe as read from file.
* @return A recipe object based on the provided string representation.
*/
public static Recipe readRecipe(String readRecipe) {
Scanner sc = new Scanner(readRecipe);
Recipe recipe;
String instructions = "None";
String recipeName = sc.nextLine().strip();
StringBuilder sb = new StringBuilder();
String line;
recipe = new Recipe(recipeName, instructions);
while (sc.hasNextLine()) {
line = sc.nextLine();
if (line.startsWith("-")) {
String[] ingredientParts = line.split("\\|");
FoodItem ingredientType = FoodItem.valueOf(ingredientParts[0].replaceFirst("-", "").strip());
double ingredientAmount = Double.parseDouble(ingredientParts[1].strip());
MeasuringUnit ingredientUnit = MeasuringUnit.valueOf(ingredientParts[2].strip());
recipe.addIngredient(ingredientType, ingredientAmount, ingredientUnit);
} else {
sb.append(line).append("\n");
}
}
recipe.setInstructions(String.valueOf(sb).strip());
return recipe;
}
/**
* The method takes in an IngredientsAtHand object and writes it to a .register-file with the provided
* String as title. The file contains no other information than a simple list of FoodItem constants
* separated by new-line. The resulting file is stored at /main/recourses/recipes/.
* An IllegalArgumentException is thrown if the ingredients at hand object is null.
* IOExceptions may occur upon writing to file, in which case the stacktrace is printed to terminal.
*
* @param ingredientsAtHand An IngredientsAtHand object that holds a collection of constants of the
* FoodItem enum class.
* @param title The title by which to name the file that the ingredients at hand are written to.
*/
public static void writeIngredientsAtHand(IngredientsAtHand ingredientsAtHand, String title) throws IOException {
StringBuilder sb = new StringBuilder();
try (FileWriter fileWriter = new FileWriter(filePath + title + fileType)) {
if (ingredientsAtHand == null) {
fileWriter.write("");
}else {
ingredientsAtHand.getIngredientsAtHand().forEach((ingredient) -> sb.append(ingredient).append("\n"));
try {
fileWriter.write(String.valueOf(sb));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
/**
* The method reads an IngredientsAtHand object from the .register file with the name provided as a parameter
* and stored at /main/recourses/recipes/. If the file is not found, a FileNotFoundException is thrown and
* null is returned instead of a IngredientsAtHand object.
* @param title Title of the file to read the IngredientsAtHand object from.
* @return An IngredientsAtHand object based on the provided .register file.
*/
public static IngredientsAtHand readIngredientsAtHand(String title) {
File file = new File(filePath + title + fileType);
IngredientsAtHand ingredientsAtHand = new IngredientsAtHand();
try (Scanner sc = new Scanner(file)) {
String line;
while (sc.hasNext()) {
line = sc.next();
if (!line.isBlank()) {
ingredientsAtHand.addIngredient(FoodItem.valueOf(line));
}
}
} catch (FileNotFoundException e) {
return null;
}
return ingredientsAtHand;
}
}
\ No newline at end of file
package no.ntnu.idatt1002.demo.data.recipes; package no.ntnu.idatt1002.demo.data.recipes;
/**
* The FoodItem enum class defines a set of food types that may be serving as ingredients in recipes and that the
* user may, or may not, have available in the cupboard, fridge or freezer. The label is a strict lower case
* version of the constant value where the underscore is replaced by a space. This value is used at the front-end
* to represent each enum value.
*
* @author hannesofie
*/
public enum FoodItem { public enum FoodItem {
ONION("onion"), ONION("onion"),
MINCED_MEAT("minced meat"), MINCED_MEAT("minced meat"),
POTATO("potatoes"), POTATO("potato"),
YELLOW_CHEESE("yellow cheese"), YELLOW_CHEESE("yellow cheese"),
WHEAT_FLOUR("wheat flour"), WHEAT_FLOUR("wheat flour"),
MILK("milk"), MILK("milk"),
TOMATO("tomato"), TOMATO("tomato"),
ORANGE("orange"), ORANGE("orange"),
LEMON("lemon"), LEMON("lemon"),
SALSA_SAUCE("salsa sauce") SALSA_SAUCE("salsa sauce"),
; CUCUMBER("cucumber"),
SALAD("salad"),
SPINACH("spinach"),
SPRING_ROLL("spring roll"),
BELL_PEPPER("bell pepper"),
CHICKPEAS("chickpeas"),
SPAGHETTI("spaghetti"),
PASTA("pasta"),
CREAM("cream"),
HONEY("honey"),
VINEGAR("vinegar"),
TOMATO_PASTE("tomato paste"),
CHILLI("chilli"),
EGG("egg"),
OLIVE_OIL("olive oil"),
HAM("ham"),
PARMESAN("parmesan"),
SNAP_PEA("snap pea"),
MACARONI("macaroni"),
SALMON("salmon"),
FISH("fish"),
CARROT("carrot"),
BUTTER("butter"),
LEEK("leek"),
BREADCRUMBS("breadcrumbs"),
OIL("oil"),
SUMMER_CUTLET("summer cutlet"),
RED_ONION("red onion"),
AVOCADO("avocado"),
LEMON_JUICE("lemon juice"),
DRY_THYME("dry thyme"),
FRESH_YEAST("fresh yeast"),
GARLIC_CLOVE("garlic clove"),
GINGER("ginger"),
CANNED_TOMATO("canned tomato"),
DRY_BASIL("dry basil"),
FRESH_BASIL("fresh basil"),
CELERY("celery"),
BROTH("broth"),
BAY_LEAF("bay leaf"),
CHILLI_BEANS("chilli beans"),
CHILLI_POWDER("chilli powder"),
CUMIN_POWDER("cumin powder"),
PIE_DOUGH("pie dough"),
BROCCOLI("broccoli"),
LAM("lam"),
SUGAR("sugar"),
SHALLOT("shallot"),
RED_WINE("red wine"),
WHITE_BEANS("white beans"),
FROZEN_GREEN_PEAS("frozen green peas"),
SAUSAGE("sausage"),
DRY_OREGANO("dry oregano");
public final String label; public final String label;
/**
* The constructor of the enum constants takes in a string label and assigns it to its respective constant.
* The label is used for representation in texts and lists at the frontend of the application.
* @param label A lower-case and readable string representation of the enum constant.
*/
FoodItem(String label) { FoodItem(String label) {
this.label = label; this.label = label;
} }
......
package no.ntnu.idatt1002.demo.data.recipes; package no.ntnu.idatt1002.demo.data.recipes;
import java.util.Objects;
/** /**
* The Ingredient class represents an ingredient that can be part of a recipe in real life and/or * The Ingredient class represents an ingredient that can be part of a recipe in real life and/or
* be available to the user in real life. When the ingredient is part of a recipe, the subclass called * be available to the user in real life. When the ingredient is part of a recipe, the subclass called
...@@ -42,6 +40,12 @@ public class Ingredient { ...@@ -42,6 +40,12 @@ public class Ingredient {
return foodType; return foodType;
} }
/**
* The method takes in a value of the FoodItem enum and sets the ingredient's foodType field equal to this constant.
* If null is given an IllegalArgumentException is thrown.
*
* @param foodType The type of food to assign the ingredient to.
*/
public void setFoodType(FoodItem foodType) { public void setFoodType(FoodItem foodType) {
if(foodType == null) { if(foodType == null) {
throw new IllegalArgumentException("The food type must be set to a valid value of FoodItem."); throw new IllegalArgumentException("The food type must be set to a valid value of FoodItem.");
...@@ -116,8 +120,4 @@ public class Ingredient { ...@@ -116,8 +120,4 @@ public class Ingredient {
return Double.compare(that.amount, amount) == 0 && foodType == that.foodType && unit == that.unit; return Double.compare(that.amount, amount) == 0 && foodType == that.foodType && unit == that.unit;
} }
/* @Override
public int hashCode() {
return Objects.hash(foodType, amount, unit);
}*/
} }
...@@ -4,81 +4,55 @@ import java.util.ArrayList; ...@@ -4,81 +4,55 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* The IngredientsAtHand class contains a collection of Ingredient objects that are currently available to the user. * The class holds a collection of FoodItem constants in an ArrayList that represents the groceries that the
* Only one instance of each ingredient type may exist in the collection. If an ingredient is already present in the * user has available in real life. The class provides methods to add and remove food types from the collection
* collection, the old will be replaced by the new. * as well as checking if a given food type is available, i.e. is part of the collection.
* *
* @author hannesofie * @author hannesofie
*/ */
public class IngredientsAtHand { public class IngredientsAtHand {
private final List<Ingredient> ingredientsAtHand = new ArrayList<>(); private final ArrayList<FoodItem> ingredientsAtHand = new ArrayList<>();
/** /**
* The method returns the collection of ingredients at hand as an arraylist of ingredient objects. * The method returns the collection of ingredients at hand as an arraylist of FoodItem enum constants.
* @return The collection of ingredients at hand to the user. * @return The collection of food types at hand to the user.
*/ */
public List<Ingredient> getIngredientsAtHand() { public List<FoodItem> getIngredientsAtHand() {
return ingredientsAtHand; return ingredientsAtHand;
} }
/** /**
* The method takes in an ingredient object and adds it to the collection of ingredients at hand. * The method takes in an ingredient object and adds it to the collection of ingredients at hand.
* @param ingredient The ingredient object to add to the collection of ingredients at hand. * @param ingredient The food type to add to the collection of ingredients at hand.
*/ */
public void addIngredient(Ingredient ingredient) { public void addIngredient(FoodItem ingredient) {
this.ingredientsAtHand.add(ingredient); if(!this.atHand(ingredient) && ingredient != null) {
this.ingredientsAtHand.add(ingredient);
}
} }
/** /**
* Returns null if no ingredient of the requested type is found in the collection. * The method takes in a constant of the FoodType enum class and checks if it is present in the collection.
* @param ingredientType What type of food the ingredient is. * If it is present, true is returned, otherwise false.
* @return The ingredient of the specified type found among the ingredients at hand, null otherwise. * @param foodItem A constant value of the FoodItem enum class to check for in the collection
* of ingredients at hand.
* @return True if the food type is at hand, otherwise false.
*/ */
public Ingredient getIngredient(FoodItem ingredientType) { public boolean atHand(FoodItem foodItem) {
if(ingredientType == null) return null; return ingredientsAtHand.stream().anyMatch( (in) -> in.equals(foodItem));
return this.getIngredientsAtHand().stream()
.filter((ingredient) -> ingredient.getFoodType() == ingredientType)
.findFirst().orElse(null);
} }
/**
* The method takes in three parameters. The method first checks if the Ingredient is at hand in the first place.
* If it is, the old amount and unit of this ingredient are replaced by the provided amount and unit if they
* differ. If not, the ingredient is left as is. If the ingredient is not in the collection,
* @param ingredientType What type of food the ingredient is.
* @param amount The amount of the ingredient.
* @return True if Ingredient is successfully altered or added, false if not.
*/
public boolean alterIngredient(FoodItem ingredientType, double amount, MeasuringUnit unit) {
//TODO: Consider handling exceptions differently.
if(ingredientsAtHand.stream().anyMatch((ingredient) -> ingredient.getFoodType() == ingredientType)) {
try {
getIngredient(ingredientType).setAmount(amount);
getIngredient(ingredientType).setUnit(unit);
} catch (IllegalArgumentException e) {
return false;
}
} else {
try {
addIngredient(new Ingredient(ingredientType, amount, unit));
} catch (IllegalArgumentException e) {
return false;
}
}
return true;
}
/** /**
* The method takes in a value of the FoodItem enum as a parameter and removes it from the collection of * The method takes in a value of the FoodItem enum as a parameter and removes it from the collection of
* ingredients at hand if it exists and returns true. If no ingredient of the given type was found in the * ingredients at hand if it exists and returns true. If no ingredient of the given type was found in the
* collection, false is returned. * collection, false is returned.
* @param ingredientType What type of food the ingredient is. * @param ingredientType What type of food to remove from the list of ingredients at hand.
* @return True if the ingredient was found among the ingredients at hand and removed, false otherwise. * @return True if the ingredient was found among the ingredients at hand and removed, false otherwise.
*/ */
public boolean removeIngredient(FoodItem ingredientType) { public boolean removeIngredient(FoodItem ingredientType) {
return ingredientsAtHand.removeIf((ingredient) -> ingredient.getFoodType() == ingredientType); return ingredientsAtHand.remove(ingredientType);
} }
} }
package no.ntnu.idatt1002.demo.data.recipes; package no.ntnu.idatt1002.demo.data.recipes;
/**
* The enum class defines a set of valid units of measurements related to the ingredients and recipes.
* The label property of each constant provides a lower-case representation of each unit that can be used in
* presentation to the user.
*
* @author hannesofie
*/
public enum MeasuringUnit { public enum MeasuringUnit {
DL("dl."), DL("dl."),
...@@ -8,10 +16,19 @@ public enum MeasuringUnit { ...@@ -8,10 +16,19 @@ public enum MeasuringUnit {
TBS("tbs."), TBS("tbs."),
GR("gr."), GR("gr."),
KG("kg."), KG("kg."),
PC("pieces"); PC("pieces"),
CAN("can"),
SLICE("slice"),
PKG("pack"),
CB("cubes");
public final String label; public final String label;
/**
* The constructor of each enum constant takes in a string representation of the constant that is more suited for
* presentation at the frontend.
* @param label Lower case representation of the unit of measure.
*/
MeasuringUnit(String label) { MeasuringUnit(String label) {
this.label = label; this.label = label;
} }
......
...@@ -4,29 +4,56 @@ import java.util.ArrayList; ...@@ -4,29 +4,56 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
// Method in case we want to check if also the amount is enough for the recipe. /**
// Recipes without any ingredients can be showed among all ingredients, but not in suggestion based on ingredients * The recipe class represents a dinner recipe for the user. It has a name, a textual instruction and
// at hand. * holds a collection of recipe ingredient items. Each recipe ingredient has a food type, amount, unit of measure and
//TODO: Record? * a status(true/false) of whether it is at hand or not.
* Apart for methods to get and alter fields, the class provides the following specialized methods:
* - updateIngredientStatus: takes in a list of food types at hand and updates the 'atHand' property of each
* of its ingredients accordingly.
* - getMissingIngredients: returns the number of ingredients in the recipe that are not at hand.
* - getMissingList: returns an ArrayList of the FoodItem labels (String) that are not at hand.
* specific food type
* When the Recipe has not been compared to a collection of ingredients at hand, the number of missing
* ingredients is set to 0 and the list of missing ingredients is set to an empty ArrayList.
*
* @author hannesofie
*/
public class Recipe { public class Recipe {
private String name = ""; private String name;
private List<RecipeIngredient> ingredientList = new ArrayList<>(); private final ArrayList<RecipeIngredient> ingredientList = new ArrayList<>();
private String instructions = ""; private String instructions;
public Recipe(String name, String description ) { private int missingIngredients = 0;
if(name.isBlank() | description.isBlank()) { private ArrayList<String> missingList = new ArrayList<>();
/**
* The constructor method creates a new Recipe object by setting the name and instructions as Strings.
* @param name Name of the recipe.
* @param instructions The instructions of the recipe.
*/
public Recipe(String name, String instructions ) {
if(name.isBlank() | instructions.isBlank()) {
throw new IllegalArgumentException("The recipe must have a name and a description."); throw new IllegalArgumentException("The recipe must have a name and a description.");
} }
this.name = name; this.name = name;
this.instructions = description; this.instructions = instructions;
} }
/**
* The method returns the name of the recipe.
* @return The name of the recipe as a String.
*/
public String getName() { public String getName() {
return name; return name;
} }
/**
* The method takes in a String to which the name of the recipe is set. If a blank String is given, an
* IllegalArgumentException is thrown.
* @param name New name for the recipe.
*/
public void setName(String name) { public void setName(String name) {
if(name.isBlank()) { if(name.isBlank()) {
throw new IllegalArgumentException("The recipe name cannot be left blank."); throw new IllegalArgumentException("The recipe name cannot be left blank.");
...@@ -34,14 +61,27 @@ public class Recipe { ...@@ -34,14 +61,27 @@ public class Recipe {
this.name = name; this.name = name;
} }
/**
* The method returns the list of RecipeIngredients that the recipe consists of.
* @return A list of RecipeIngredients belonging to the recipe.
*/
public List<RecipeIngredient> getIngredientList() { public List<RecipeIngredient> getIngredientList() {
return ingredientList; return ingredientList;
} }
/**
* The method returns the instructions of the recipe.
* @return The instructions of the recipe as a String.
*/
public String getInstructions() { public String getInstructions() {
return instructions; return instructions;
} }
/**
* The method takes a String as a parameter to which the recipe instructions are set, provided that the String
* is not blank, in which case an IllegalArgumentException is thrown instead.
* @param instructions The new instructions of the recipe as a String.
*/
public void setInstructions(String instructions) { public void setInstructions(String instructions) {
if(instructions.isBlank()) { if(instructions.isBlank()) {
throw new IllegalArgumentException("The recipe instructions cannot be left blank."); throw new IllegalArgumentException("The recipe instructions cannot be left blank.");
...@@ -49,13 +89,12 @@ public class Recipe { ...@@ -49,13 +89,12 @@ public class Recipe {
this.instructions = instructions; this.instructions = instructions;
} }
/**
/* public void addIngredients(ArrayList<RecipeIngredient> ingredients) { * The method takes in a constant of the FoodItem enum class and searches for it among the recipe's ingredients.
ingredients.forEach((ingredient) -> this.ingredientList.add(ingredient)); * If it is found, the recipe ingredient object is returned, if it is not found, null is returned.
}*/ * @param ingredientType A constant value defined by the FoodItem enum class.
* @return The recipe ingredient if it is present in the recipe ingredient list, null otherwise.
//TODO: Make interface for "collects ingredients" and do deep copy. */
public RecipeIngredient getIngredient(FoodItem ingredientType) { public RecipeIngredient getIngredient(FoodItem ingredientType) {
if(ingredientType == null) return null; if(ingredientType == null) return null;
return this.getIngredientList().stream() return this.getIngredientList().stream()
...@@ -63,7 +102,17 @@ public class Recipe { ...@@ -63,7 +102,17 @@ public class Recipe {
.findFirst().orElse(null); .findFirst().orElse(null);
} }
/**
* The method adds an ingredient to the recipe if it is not already in the recipe, in which case the existing
* ingredient of the same food type is updated with the parameters provided to this method. The parameters are
* a constant of the FoodItem enum class to provide the food type, a double value representing the amount of
* the ingredient and the measuring unit given as a constant of the MeasuringUnit enum class. If the
* ingredient is neither added nor updated successfully, false is returned, otherwise true is returned.
* @param ingredientType The type of food the ingredient belongs to.
* @param amount The amount of the ingredient when part of this recipe.
* @param unit The measuring unit of the ingredient part of this recipe.
* @return True if the ingredient is successfully added or altered, otherwise false.
*/
public boolean addIngredient(FoodItem ingredientType, double amount, MeasuringUnit unit) { public boolean addIngredient(FoodItem ingredientType, double amount, MeasuringUnit unit) {
if(ingredientList.stream().anyMatch((ingredient) -> ingredient.getFoodType() == ingredientType)) { if(ingredientList.stream().anyMatch((ingredient) -> ingredient.getFoodType() == ingredientType)) {
try { try {
...@@ -84,34 +133,85 @@ public class Recipe { ...@@ -84,34 +133,85 @@ public class Recipe {
} }
/** /**
* The functionality may be expanded upon in order to also check if the amount is sufficient for the current recipe. * The method takes in an object of the IngredientsAtHand class which defines which foods the user has at hand.
* @param ingredientsAtHand * If the IngredientsAtHand object is null, and IllegalArgumentException is thrown. Otherwise, the recipe's
* ingredient list is iterated through, and the 'atHand' property of each ingredient is updated to true if
* it is present in the ingredients at hand collection, or false if it is not.
* In addition, the recipe's parameters "missingIngredients" and "missingList" are updated with the final
* number of ingredients in the recipe that are not at hand and the name of those recipes as Strings in an
* ArrayList, respectively.
* @param ingredientsAtHand An IngredientsAtHand object holding a collection of the food types that the
* user has available.
*/ */
public void updateIngredientStatus(IngredientsAtHand ingredientsAtHand) { public void updateIngredientStatus(IngredientsAtHand ingredientsAtHand) {
// Will need a supporting class for converting between units to be accurate. if (ingredientsAtHand == null) {
if(ingredientsAtHand == null) { throw new NullPointerException("The ingredients at hand object must exist");
throw new NullPointerException("The ingredients at hand object must exist"); } else {
} else if (ingredientsAtHand.getIngredientsAtHand().size() < 1) { missingList = new ArrayList<>();
throw new IllegalArgumentException("The collection of ingredients at hand is empty."); missingIngredients = (int) ingredientList.stream().filter((inRecipe) -> !ingredientsAtHand.atHand(inRecipe.getFoodType())).count();
} else {
ingredientList.forEach((inRecipe) -> { ingredientList.forEach((inRecipe) -> {
ingredientsAtHand.getIngredientsAtHand().forEach((atHand) -> { inRecipe.setAtHand(ingredientsAtHand.atHand(inRecipe.getFoodType()));
if(inRecipe.getFoodType() == atHand.getFoodType()) { if(!ingredientsAtHand.atHand(inRecipe.getFoodType())) {
inRecipe.setAtHand(true); missingList.add(inRecipe.getFoodType().label);
} }
}); });
}); }
} }
/**
* The method returns the property 'missingIngredients' of the recipe. This is the number of ingredients
* as an int, contained in teh recipe, that is not part of the collection of ingredients at hand last updated for.
* @return The number of ingredients in the recipe that are not currently at hand.
*/
public int getMissingIngredients() {
return missingIngredients;
}
/**
* The method returns an ArrayList of Strings representing each of the food types that the recipe is missing
* relative to the collection of ingredients at hand last updated for.
* @return The ingredients in this recipe that are not currently at hand as a list of Strings.
*/
public ArrayList<String> getMissingList() {
return missingList;
} }
/**
* The method returns a String representation of the recipe, listing its name, ingredients and instructions.
* @return A String representation of the recipe.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.name).append("\n");
this.getIngredientList().forEach((ingredient) -> sb.append(ingredient).append("\n"));
sb.append(this.getInstructions());
return String.valueOf(sb);
}
/**
* The method takes in another Object and checks whether it is equal to the current recipe object. It first checks
* if the object reference is the same, in which case true is returned. If not, it checks that the object is not
* null and whether its class is dissimilar, for which false is also returned. Finally, the object is cast as a
* Recipe and compared by name. If the two recipe's names are the same, they are considered the same.
* @param o A general Object.
* @return True if the object is equal to the current recipe, false otherwise.
*/
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (!(o instanceof Recipe recipe)) return false;
Recipe recipe = (Recipe) o;
return Objects.equals(name, recipe.name); return Objects.equals(name, recipe.name);
} }
/**
* The method returns a standard hash-code based on the recipe's name, ingredient list and instructions.
* @return A hash-code as an integer for the recipe.
*/
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(name, ingredientList, instructions); return Objects.hash(name, ingredientList, instructions);
......
...@@ -35,10 +35,24 @@ public class RecipeIngredient extends Ingredient{ ...@@ -35,10 +35,24 @@ public class RecipeIngredient extends Ingredient{
/** /**
* The method sets the value of the atHand field for the ingredient to either true or false. * The method sets the value of the atHand field for the ingredient to either true or false.
* @param atHand * @param atHand A boolean value to set the 'atHand' status of the ingredient.
*/ */
public void setAtHand(boolean atHand) { public void setAtHand(boolean atHand) {
this.atHand = atHand; this.atHand = atHand;
} }
/**
* The method returns a String representation of a Recipe Ingredient object, listing its type, amount, unit
* and whether it is at hand or not.
* @return A String representation of the recipe ingredient object.
*/
@Override
public String toString() {
return "Ingredient{" +
"foodType=" + this.getFoodType().label +
", amount=" + this.getAmount() +
", unit=" + this.getUnit().label +
", at hand=" + this.isAtHand() +
'}';
}
} }
package no.ntnu.idatt1002.demo.data.recipes; package no.ntnu.idatt1002.demo.data.recipes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/**
* The RecipeRegister class holds a collection of Recipes in a list and provides methods to access the recipes,
* add recipes to the register as well as to pick a number of recipe's best fitting a list of food types
* provided as a IngredientsAtHand object. The register is instantiated using the default constructor.
*
* @author hannesofie
*/
public class RecipeRegister { public class RecipeRegister {
private final List<Recipe> recipes = new ArrayList<>(); private ArrayList<Recipe> recipes = new ArrayList<>();
//TODO: Copy-constructor /**
* The method returns the list of recipes in the Register.
public List<Recipe> getRecipes() { * @return A list of recipes that belong to this register.
return recipes; */
public ArrayList<Recipe> getRecipes() {
return this.recipes;
} }
/**
* The method takes in a Recipe object. If the recipe is already in the register, the old recipe will be replaced.
* If the recipe is null, an IllegalArgumentException is thrown. Otherwise, the new recipe is added to the
* register's collection of recipes.
* @param recipe A new recipe to add to the register.
*/
public void addRecipe (Recipe recipe) { public void addRecipe (Recipe recipe) {
if(recipes.contains(recipe)) { if(this.recipes.contains(recipe)) {
throw new IllegalArgumentException("The recipe already exists in the register."); // Or just replace? recipes.remove(getRecipe(recipe.getName()));
recipes.add(recipe);
} else if (recipe == null) { } else if (recipe == null) {
throw new NullPointerException("The recipe cannot be null."); throw new NullPointerException("The recipe cannot be null.");
} else {
this.recipes.add(recipe);
} }
this.recipes.add(recipe);
} }
/**
* The method takes in a string with the name of a recipe and returns that recipe object if it is in the
* recipe register. If it is not found, null is returned instead.
* @param name The name of a recipe as a String.
* @return The recipe matching the provided name if it is present in the recipe register.
*/
public Recipe getRecipe(String name) { public Recipe getRecipe(String name) {
return recipes.stream(). return recipes.stream().
filter((recipe) -> recipe.getName().equals(name)) filter((recipe) -> recipe.getName().equals(name))
.findFirst().orElse(null); .findFirst().orElse(null);
} }
/**
* The method takes in a number and an IngredientsAtHand object and returns an ArrayList containing that
* number of recipes that, based on the ingredients at hand, require the fewest extra ingredients apart
* from the ones at hand. If the number of recipes in the register is fewer than the provided number, the list
* will contain as many as the register can provide. The resulting list is sorted by the number of ingredients
* required apart from those ingredients at hand. If zero recipes are requested, and empty ArrayList is returned.
* @param number Number of recipes to pick from the recipe register.
* @param atHand A IngredientsAtHand object providing a collection of the food types at hand to the user.
* @return A list of recipes of sorted by available ingredients and limited to the provided number or the
* number of recipes in the register.
*/
public ArrayList<Recipe> pickBestFits(int number, IngredientsAtHand atHand) {
if(number == 0) {
return new ArrayList<>();
}
ArrayList<Recipe> recipes;
this.recipes.forEach(r -> r.updateIngredientStatus(atHand));
recipes = this.recipes.stream()
.sorted(Comparator.comparingInt(Recipe::getMissingIngredients))
.limit(number).collect(Collectors.toCollection(ArrayList::new));
return recipes;
}
} }
...@@ -9,3 +9,9 @@ amount=200.0 ...@@ -9,3 +9,9 @@ amount=200.0
isRecurring=Not recurring isRecurring=Not recurring
category=FOOD category=FOOD
date=2023-04-15
description=iphone
amount=5000.0
isRecurring=Not recurring
category=OTHER
MILK
YELLOW_CHEESE
MINCED_MEAT
ONION
HAM
TOMATO
WHEAT_FLOUR
ORANGE
OIL
# Simple Pasta Salad
- PASTA | 300 | GR
- OLIVE_OIL | 2 | TBS
- HAM | 10 | SLICE
- PARMESAN | 100 | GR
- SNAP_PEA | 50 | GR
1. Kok pasta som anvist på pakken.
2. Hell av vannet og tilsett salt og olivenolje. Avkjøl pastaen.
3. Skjær skinke, ost og sukkererter(snap pea) i små biter og tilsett pastaen. Rør rundt.
4. Smak til med salt og pepper og drypp over litt olivenolje.
https://www.matprat.no/oppskrifter/rask/enkel-pastasalat/
# Fish gratin
- MACARONI | 75 | GR
- BUTTER | 2 | TBS
- WHEAT_FLOUR | 3 | TBS
- MILK | 4 | DL
- LEEK | 0.25 | PC
- FISH | 400 | GR
- EGG | 4 | PC
- YELLOW_CHEESE | 1 | DL
- BREADCRUMBS | 0.5 | DL
1. Kok makaroni som anvist på pakken, hvis ikke du har rester med ferdig kokt pasta. Ca. 250 g kokt pasta tilsvarer 75 g ukokt makaroni.
2. Smelt smør i en kjele ved svak varme. Tilsett hvetemel og rør blandingen jevn. Skru ned varmen. Tilsett litt og litt av melken og rør klumpfritt mellom hver gang. Kok opp mens du rører godt. La sausen småkoke i ca. 5 minutter.
3. Ta sausen av platen og tilsett kokt makaroni, tynne purreringer, salt og pepper. Ha i biter med kokt fisk. Både fersk, salt og røkt fisk kan brukes.
4. Skill eggene, og stivpisk eggehvitene. Bland først eggeplommene inn i sausen. Vend deretter inn eggehvitene litt forsiktig for å beholde luften i eggehvitene.
Det beste er å bruke romtempererte egg. Da blir det mer luft i eggehvitene når du pisker dem. Pass på at det ikke kommer eggeplomme opp i eggehviten når du skiller eggene. Eggeplommen inneholder fett og gjør at det blir vanskelig å stivpiske eggehvitene.
5. Fordel gratengrøren i en godt smurt ildfast form og strø revet ost og griljermel på toppen. Stek i stekeovnen ved 180 °C i ca. 40 minutter til gratengen har hevet seg og blitt gyllen.
Server fiskegrateng med råkost og kokte poteter.
Steketiden vil variere etter hva slags form du bruker. Bruker du en flat form blir steketiden kortere enn om du fyller en form med høyere kanter.
https://www.matprat.no/oppskrifter/familien/fiskegrateng/
# Summer Cutlets with Crispy Pasta Salad
- SUMMER_CUTLET | 4 | PC
- OIL | 2 | TBS
- PASTA | 150 | GR
- SNAP_PEA | 150 | GR
- CARROT | 2 | PC
- RED_ONION | 1 | PC
- AVOCADO | 1 | PC
- LEMON_JUICE | 3 | TBS
- OLIVE_OIL | 2 | TBS
- DRY_THYME | 1 | TSP
1. Kok pasta etter anvisning på pakken. Hell den i et dørslag og skyll med kaldt vann.
2. Rens og finsnitt sukkererter og del skrelte gulrøtter, rødløk og avokado i små biter. Hell på sitronsaft, olivenolje, timian, salt og pepper. Bland godt og tilsett makaronien.
3. Pensle kotelettene med olje. Dryss på krydder.
4. Grill kotelettene 4-5 minutter på hver side.
https://www.matprat.no/oppskrifter/familien/sommerkoteletter-med-spro-pastasalat/
# Basic Squared pizza
- WHEAT_FLOUR | 7 | DL
- OIL | 4 | TBS
- FRESH_YEAST | 0.5 | PKG
- MINCED_MEAT | 400 | GR
- ONION | 1 | PC
- GARLIC_CLOVE | 2 | PC
- TOMATO_PASTE | 2 | TBS
- CANNED_TOMATO | 1 | CAN
- DRY_BASIL | 1 | TSP
- YELLOW_CHEESE | 300 | GR
- BELL_PEPPER | 1 | PC
1. Rør ut gjær i 3 dl lunkent vann. Tilsett olje, salt og mel. Elt deigen godt sammen, og la den heve til dobbel størrelse.
2. Brun kjøttdeig i olje på sterk varme i to omganger. Ha kjøttdeigen tilbake i stekepannen.
3. Tilsett hakket løk, hvitløk, tomatpuré, hermetiske tomater og ca. 1 dl vann. La sausen småkoke i ca. 10 minutter til den tykner. Smak til med salt, pepper og basilikum (alt. 1 ss fersk basilikum).
4. Kjevle ut deigen. Ha den over på en bakepapirkledd langpanne og fordel kjøttsausen utover deigen. Dryss over revet ost, og ha på paprika.
5. Stek pizzaen midt i ovn på 225 °C i ca. 20 minutter.
Server toppet med oregano og gjerne en frisk salat med en god dressing som tilbehør til pizzaen.
https://www.matprat.no/oppskrifter/kos/langpannepizza/
# Pasta Bolognese
- MINCED_MEAT | 600 | GR
- OIL | 4 | TBS
- ONION | 1 | PC
- GARLIC_CLOVE | 3 | PC
- CARROT | 1 | PC
- CELERY | 1 | PC
- CANNED_TOMATO | 2 | CAN
- BROTH | 6 | DL
- BAY_LEAF | 1 | PC
- SPAGHETTI | 500 | GR
1. Skrell og finhakk løk og hvitløk. Vask og finhakk gulrot og stilkselleri.
2. Stek kjøttdeigen i flere omganger i en varm panne med olje. Ha kjøttdeigen over i en gryte og ha i de finhakkede grønnsakene. Ha i tomater, 0,5 dl rødvin om du har det for hånden, kjøttbuljong og laurbærblad.
3. La det hele småkoke under lokk i ca. 1 ½ time. Tilsett mer væske om nødvendig. Smak til med salt og pepper.
4. Kok pasta etter anvisning på pakken. Vend litt av sausen inn i pastaen rett før servering, da holder pastaen seg bedre og vil holde seg varmere.
I Bologna finhakker de kjøttet til den ekte bolognesen. Bytt ut kjøttdeig med flatbiff av storfe. Følg samme fremgangsmåte, men la Bolognesen koke lengre. Etter to timer er kjøttet veldig mørt. Tilsett mer væske om nødvendig.
https://www.matprat.no/oppskrifter/gjester/pasta-bolognese/
# Chilli con Carne
- MINCED_MEAT | 400 |GR
- OIL | 2 | TBS
- ONION | 1 | PC
- CHILLI | 1 | PC
- GARLIC_CLOVE | 2 | PC
- CANNED_TOMATO | 1 | CAN
- BELL_PEPPER | 2 | PC
- CHILLI_BEANS | 1 | CAN
- CHILLI_POWDER | 1.5 | TSP
- CUMIN_POWDER | 0.5 | TSP
Bruk gjerne paprika i ulike farger!
1. Ha margarin eller olje i en varm gryte. Vent til margarinen slutter å bruse, og brun kjøttdeig i to omganger sammen med løk, hvitløk og chili.
2. Hell over hermetisk tomat og la det surre i 3-4 minutter.
3. Bland inn chilibønner, paprika og krydder. La det koke et par minutter og smak til med salt.
Server gjerne gryteretten med salat og grove minibaguetter.
https://www.matprat.no/oppskrifter/rask/chili-con-carne/
# Ham Pie
- PIE_DOUGH | 1 | PKG
- BROCCOLI | 100 | GR
- GARLIC_CLOVE | 1 | PC
- LEEK | 0.25 | PC
- EGG | 4 | PC
- MILK | 2 | DL
- YELLOW_CHEESE | 1.5 | DL
Forvarm stekeovnen til 200 °C.
1. Legg deigen i en liten paiform (20-22 cm) og klem den godt inn til kantene. La det øverste papiret som deigen er pakket inn i sitte på, og fyll formen med tørkede erter eller ris.
2. Sett paiformen midt i stekeovnen og forstek paiskallet i 12-15 minutter, til kantene begynner å få farge. Hell erter/ris ut av formen (ta vare på dem til neste gang du aker pai) og fjern papiret.
3. Del i mellomtiden brokkoli i små buketter og forvell dem i kokende vann i 2-3 minutter. Skyll med kaldt vann og la dem renne godt av seg.
4. Ha skinke, hvitløk, purre og brokkolibuketter over bunnen.
5. Visp sammen egg og melk (ev. fløte). Krydre med salt og pepper. Hell eggeblandingen over og dryss revet ost på toppen.
6. Senk varmen i stekeovnen til 180 °C og stek paien midt i ovnen i 25-30 minutter, eller til eggeblandingen har stivnet og paien har fått en fin, gyllenbrun farge.
Server paien nystekt eller lun, gjerne med en god, frisk salat ved siden av.
Har du ikke akkurat de ingrediensene som står her, kan du ha nesten hva som helst i en pai. Pølse- eller kjøttrester i terninger, og masse forskjellige grønnsaker smaker godt. Husk å forvelle harde grønnsaker som løk og brokkoli.
https://www.matprat.no/oppskrifter/familien/skinkepai/
# French Lam Stew
- LAM | 1 | KG
- BUTTER | 3 | TBS
- SHALLOT | 10 | PC
- GARLIC_CLOVE | 2 | PC
- RED_WINE | 2.5 | DL
- CANNED_TOMATO | 1 | CAN
- TOMATO_PASTE | 1 | DL
- CARROT | 2 | PC
- DRY_THYME | 1 | TSP
- BAY_LEAF | 1 |PC
- WHITE_BEANS | 1 | CAN
1. Varm en stekepanne med smør og stek kjøttet på alle sider. Legg det over i en vid gryte. Stek sjalottløk raskt i samme panne og ha over i gryten. Hell i vin, tomater, tomatpuré, gulrot i biter og krydder. Tilsett nok vann til at det akkurat dekker. Kok opp, senk varmen og sett på lokk. La gryten småkoke til kjøttet er helt mørt, ca. 1 time.
2. Skyll bønnene (ferdig kokt, ca 150gr) godt og ha dem i gryten. Smak til med sukker, salt og pepper.
3. Server gryten rykende varm med godt, ferskt brød og/eller poteter.
Bytt gjerne ut rødvin med hvitvin. Dette er en fin rett du kan lage om du har rester av lammestek. Kombiner gjerne med smakfulle pølser.
Du kan gjerne bruke fårikålkjøtt av får istedet for av lam. Beregn i så fall lengre koketid. Fårekjøtt trenger ca. 3 timers koketid.
https://www.matprat.no/oppskrifter/gjester/fransk-lammegryte/
# Tomato Soup with Egg
- EGG | 4 | PC
- ONION | 1 | PC
- GARLIC_CLOVE | 2 | PC
- OLIVE_OIL | 2 | TBS
- TOMATO_PASTE | 4 | TBS
- CANNED_TOMATO | 2 | CAN
- BROTH | 2 | CB
- SUGAR | 1 | TBS
- DRY_BASIL | 1 | TSP
1. Kok egg i 8-10 minutter. Avkjøl i kaldt vann og skrell dem.
2. Fres løk og hvitløk myk og blank i en kjele med litt olje.
3. Tilsett tomatpuré, og la det surre ett minutt. Ha deretter i hakket tomat, ca. 8 dl. vann, grønnsaksbuljong og sukker. Kok opp og la det småkoke i ca. 10 minutter.
4. Kjør suppen til ønsket konsistens med stavmikser eller i hurtigmikser. Tilsett basilikum (av. en halv potte fersk), og smak til med pepper.
Server suppen med kokte egg.
Prøv deg fram med ulike urter – oregano, timian, rosmarin, persille og kjørvel passer sammen med tomat. Tilsett gjerne en fløteskvett om du vil ha suppen litt mildere.
https://www.matprat.no/oppskrifter/familien/tomatsuppe-med-kokt-egg/
# Sousage Pasta Pot
- SAUSAGE | 400 | GR
- BROCCOLI | 0.5 | PC
- ONION | 1 | PC
- GARLIC_CLOVE | 1 | PC
- PASTA | 400 | GR
- CANNED_TOMATO | 2 | CAN
- DRY_OREGANO | 1 | TSP
- YELLOW_CHEESE | 100 | GR
- CREAM | 2 | DL
- SUGAR | 1 | TBS
- FROZEN_GREEN_PEAS | 100 | GR
Du kan velge mellom å bruke vann eller utblandet buljong i denne oppskriften.
1. Fjern skinnet på pølsene, og skjær dem i 1/2 cm tykke skiver. Kutt brokkoli i små buketter. Husk også å ta med stilken - kutt den i grove biter. Sett pølser og brokkoli til side.
2. Rens løk og hvitløk. Grovhakk løken og finhakk hvitløken.
3. Ha pastaskruer, hermetiske tomater, 4 dl buljong/vann og oregano i en stor gryte sammen med løk og hvitløk. Rør litt rundt så alt blander seg. Sett gryten på platen , og kok opp. Sett på lokk, og la det koke i ca. 10 minutter. Rør innimellom slik at det ikke svir seg i bunnen av gryten. Dersom du syns det er litt lite væske kan du ha i mer vann.
4. Når gryten har småkokt under lokk i ca. 10 minutter, har du i pølser og brokkoli. Kok til alt er gjennomvarmt, og pastaen er "al dente", ca. 5 minutter til.
5. Rør inn revet hvitost og fløte. Smak til med salt, pepper og eventuelt litt sukker.
6. Ha i erter helt til slutt. Da får de akkurat nok varme, uten å miste den fine grønnfargen sin.
Dryss eventuelt over litt finhakket bladpersille og litt grovkvernet pepper til pynt.
https://www.matprat.no/oppskrifter/familien/polsegryte/
\ No newline at end of file
# Meat, cheese and potatoes
- MINCED_MEAT | 500.0 | GR
- POTATO | 750.0 | GR
- YELLOW_CHEESE | 2.0 | DL
Instructions.
Testing another line of instructions!
# Meat, cheese and lemons
- MINCED_MEAT | 500.0 | GR
- LEMON | 750.0 | GR
- YELLOW_CHEESE | 2.0 | DL
Instructions.
\ No newline at end of file
.button-style {
//-fx-background-color: linear-gradient(#8ca45b, #1e5b5b);
-fx-background-color: white;
-fx-text-fill: black;
-fx-background-radius: 30;
-fx-background-insets: 0;
-fx-font-size: 16;
//-fx-text-fill: white;
}
.button-style:hover {
-fx-scale-x: 1.05;
-fx-scale-y: 1.05;
-fx-scale-z: 1.05;
}
.recipe-tile {
-fx-background-color: rgba(255, 213, 0, 0.86);
-fx-background-radius: 25;
-fx-border-color: white;
-fx-border-width: 6px;
-fx-border-radius: 25;
}
.recipe-tile:hover {
-fx-scale-x: 1.05;
-fx-scale-y: 1.05;
-fx-scale-z: 1.05;
}
.dialog-pane {
-fx-background-color: rgba(151, 175, 151, 0.8);
}
.list-cell:filled:selected:focused, .list-cell:filled:selected {
-fx-background-color: #1e5b5b;
-fx-text-fill: white;
}
.list-cell{
-fx-font-size:18.0;
}
.list-cell:even { /* <=== changed to even */
-fx-background-color: white;
}
.list-cell:odd { /* <=== changed to even */
-fx-background-color: rgba(190, 217, 190, 0.8);
}
.list-cell:filled:hover {
-fx-background-color: #1e5b5b;
-fx-text-fill: white;
}
.tile-button {
-fx-background-color: transparent;
-fx-alignment: center;
}
.ingredient:hover {
-fx-scale-x: 1.05;
-fx-scale-y: 1.05;
-fx-scale-z: 1.05;
}
.recipe-instructions {
-fx-font-size: 16;
-fx-font-style: italic;
-fx-spacing: 1.5;
}
.information-label {
-fx-background-color: rgba(255, 255, 255, 0.65);
-fx-text-fill: black;
-fx-border-radius: 20;
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment