CookbookView.java
package stud.ntnu.idatt1005.pantrypal.views;
import static javafx.stage.Screen.getPrimary;
import java.util.ArrayList;
import java.util.List;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import stud.ntnu.idatt1005.pantrypal.controllers.CookbookController;
import stud.ntnu.idatt1005.pantrypal.enums.ButtonEnum;
import stud.ntnu.idatt1005.pantrypal.enums.Route;
import stud.ntnu.idatt1005.pantrypal.models.Recipe;
import stud.ntnu.idatt1005.pantrypal.utils.NodeUtils;
import stud.ntnu.idatt1005.pantrypal.utils.Sizing;
import stud.ntnu.idatt1005.pantrypal.views.components.CookbookRecipeComponent;
import stud.ntnu.idatt1005.pantrypal.views.components.StyledButton;
/**
* The CookbookView class is responsible for creating and managing the view for the
* cookbook in the application.
* It extends the View class and uses a CookbookController to interact with the model.
* The CookbookView displays a collection of recipes in a grid-like structure.
* The number of recipes per row and the spacing between them can be adjusted.
* Each recipe is represented by a CookbookRecipeComponent.
*/
public class CookbookView extends View {
/**
* The number of recipes per row in the view.
*/
private static final int RECIPES_PER_ROW = 4;
/**
* The spacing between the recipes in the view.
*/
private final double spacing;
/**
* A map containing the recipes to be displayed in the view.
*/
private final CookbookController controller;
private final VBox pageContainer;
/**
* Constructs a CookbookView with a given CookBookController.
* It initializes the amount of recipes per row, calculates the spacing between them,
* and retrieves the recipes from the controller.
* It then creates the view.
*
* @param controller the CookBookController that this view interacts with
*/
public CookbookView(CookbookController controller) {
super(controller, Route.COOKBOOK, "/styles/cookbook.css");
this.controller = controller;
this.setScrollPane();
this.pageContainer = new VBox();
spacing = calculateSpacing();
addSearchBar();
render(this.controller.getCurrentSearch());
}
/**
* Adds a search bar to the view.vThe search bar contains a text field for searching
* recipes and a button for adding a new recipe.
*/
private void addSearchBar() {
TextField searchField = createSearchField();
StyledButton addRecipe = createAddRecipeButton();
StackPane searchBar = new StackPane();
searchBar.getChildren().addAll(searchField, addRecipe);
NodeUtils.addChildren(pageContainer, searchBar);
searchField.textProperty().addListener((observable, oldValue, newValue) ->
this.controller.searchRecipes(newValue));
}
/**
* Creates the view for the cookbook.
* It creates a VBox to contain the rows of recipes, and an HBox for each row.
* It then adds the CookbookRecipeComponents to the rows and the rows to the container.
*/
public void render(List<Recipe> currentSearch) {
VBox recipeContainer = createRecipeContainer(currentSearch);
if (pageContainer.getChildren().size() < 2) {
NodeUtils.addChildren(pageContainer, recipeContainer);
} else {
pageContainer.getChildren().set(1, recipeContainer);
}
getBorderPane().setCenter(pageContainer);
}
/**
* Creates a VBox to contain the rows of recipes.
* It creates an HBox for each row and adds the CookbookRecipeComponents to the rows.
*
* @param currentSearch the list of recipes to be displayed
* @return the VBox containing the rows of recipes
*/
private VBox createRecipeContainer(List<Recipe> currentSearch) {
VBox recipeContainer = new VBox(spacing / 2);
recipeContainer.setPadding(new Insets(spacing, 0, spacing, 0));
HBox row = new HBox(spacing);
ArrayList<Recipe> recipes = new ArrayList<>(currentSearch);
recipes.sort((a, b) -> Boolean.compare(b.getIsFavorite(), a.getIsFavorite()));
for (Recipe recipe : recipes) {
if (row.getChildren().size() >= RECIPES_PER_ROW) {
row.setAlignment(Pos.CENTER);
recipeContainer.getChildren().add(row);
row = new HBox(spacing);
}
CookbookRecipeComponent recipeComponent = new CookbookRecipeComponent(recipe);
recipeComponent.addObserver(controller);
row.getChildren().add(recipeComponent);
}
row.setAlignment(Pos.CENTER);
recipeContainer.getChildren().add(row);
return recipeContainer;
}
/**
* Creates a button for adding a new recipe.
* The button is styled and has an action that notifies the observers of the view.
*
* @return the styled button for adding a new recipe
*/
private StyledButton createAddRecipeButton() {
StyledButton button = new StyledButton("Add Recipe");
button.setOnAction(e -> notifyObservers(ButtonEnum.ADD));
StackPane.setAlignment(button, Pos.CENTER_RIGHT);
return button;
}
/**
* Creates a text field for searching recipes.
* The text field is styled and has an action that notifies the observers of the view.
*
* @return the styled text field for searching recipes
*/
private TextField createSearchField() {
TextField searchField = new TextField();
searchField.setPromptText("Search");
NodeUtils.addClasses(searchField, "search-field");
searchField.setMaxWidth(Sizing.getScreenWidth());
searchField.setMinWidth(Sizing.getScreenWidth());
searchField.textProperty().addListener((observable, oldValue, newValue) ->
this.controller.searchRecipes(newValue));
return searchField;
}
/**
* Calculates the spacing between the recipes based on the width of the screen,
* the width of the recipes, and the number of recipes per row.
*
* @return the calculated spacing
*/
private double calculateSpacing() {
Rectangle2D visualBounds = getPrimary().getVisualBounds();
return ((visualBounds.getWidth() - RECIPES_PER_ROW * CookbookRecipeComponent.getComponentWidth())
/ (RECIPES_PER_ROW + 1));
}
}