From 0814037f61d23c648092ead981af210d1c22d1da Mon Sep 17 00:00:00 2001
From: Lukas Karsch <lk224@hdm-stuttgart.de>
Date: Thu, 8 Jun 2023 18:19:38 +0200
Subject: [PATCH] Did some work on the recipe creator (still wip). added 3 new
 GUI components

---
 src/main/java/mi/hdm/TastyPages.java          |   5 +-
 .../hdm/components/CategoryPreviewLabel.java  |  23 ++++
 .../IngredientSearchResultLabel.java          |  20 +++
 .../components/SelectedIngredientLabel.java   |  41 +++++++
 .../hdm/controllers/MainPageController.java   |   1 +
 .../controllers/RecipeCreatorController.java  | 116 +++++++++++++++++-
 src/main/java/mi/hdm/controllers/View.java    |   7 +-
 src/main/java/mi/hdm/recipes/Category.java    |   2 +-
 .../java/mi/hdm/recipes/RecipeSearch.java     |  15 +++
 src/main/resources/fxml/recipe-creator.fxml   | 106 ++++++++++++----
 10 files changed, 309 insertions(+), 27 deletions(-)
 create mode 100644 src/main/java/mi/hdm/components/CategoryPreviewLabel.java
 create mode 100644 src/main/java/mi/hdm/components/IngredientSearchResultLabel.java
 create mode 100644 src/main/java/mi/hdm/components/SelectedIngredientLabel.java

diff --git a/src/main/java/mi/hdm/TastyPages.java b/src/main/java/mi/hdm/TastyPages.java
index f0dac15..c088745 100644
--- a/src/main/java/mi/hdm/TastyPages.java
+++ b/src/main/java/mi/hdm/TastyPages.java
@@ -76,13 +76,16 @@ public class TastyPages {
     }
 
     public void createRecipe() {
+        Category category = new Category("Mittagessen", 0x32d9b7);
+        categoryManager.addCategory(category);
+
         //TODO: write correct default recipe for demo (create it in app and save it from there -> no need to search ingredient keys)
         String name = "Something";
         //ingredientManager.getAllIngredients().forEach((k, v) -> System.out.println(k + " : " + v));
         Map<RecipeComponent, Integer> ingredients = Map.of(ingredientManager.getIngredient("i1401213838").get(), 5);
         String description = "10-Minuten-Rezept";
         List<String> preparation = List.of("Put it in a pan and heat it.");
-        List<Category> categories = List.of();
+        List<Category> categories = List.of(category);
         Recipe recipe1 = new Recipe(name, ingredients, description, preparation, categories, 10);
         recipeManager.addRecipe(recipe1);
     }
diff --git a/src/main/java/mi/hdm/components/CategoryPreviewLabel.java b/src/main/java/mi/hdm/components/CategoryPreviewLabel.java
new file mode 100644
index 0000000..57aa78b
--- /dev/null
+++ b/src/main/java/mi/hdm/components/CategoryPreviewLabel.java
@@ -0,0 +1,23 @@
+package mi.hdm.components;
+
+import javafx.scene.control.Label;
+import mi.hdm.recipes.Category;
+
+/**
+ * Label UI element for categories with the appropriate background color
+ */
+public class CategoryPreviewLabel extends Label {
+    private final Category category;
+
+    public CategoryPreviewLabel(Category category) {
+        super(category.getName());
+        this.category = category;
+
+        //TODO: set text color to white when background is below a certain brightness level
+        this.setStyle("-fx-background-color:" + category.getColourCode() + ";" + "-fx-padding: 4px;");
+    }
+
+    public Category getAssociatedCategory() {
+        return category;
+    }
+}
diff --git a/src/main/java/mi/hdm/components/IngredientSearchResultLabel.java b/src/main/java/mi/hdm/components/IngredientSearchResultLabel.java
new file mode 100644
index 0000000..b4c8189
--- /dev/null
+++ b/src/main/java/mi/hdm/components/IngredientSearchResultLabel.java
@@ -0,0 +1,20 @@
+package mi.hdm.components;
+
+import javafx.scene.Cursor;
+import javafx.scene.control.Label;
+import mi.hdm.recipes.RecipeComponent;
+
+public class IngredientSearchResultLabel extends Label {
+    public IngredientSearchResultLabel(RecipeComponent component) {
+        super(component.getName());
+        setStyle("-fx-padding: 4px;");
+        setOnMouseEntered(e -> {
+            setStyle("-fx-padding: 4px;" + "-fx-background-color: #DEDEDE;");
+            setCursor(Cursor.HAND);
+        });
+        setOnMouseExited(e -> {
+            setStyle("-fx-padding: 4px;");
+            setCursor(Cursor.DEFAULT);
+        });
+    }
+}
diff --git a/src/main/java/mi/hdm/components/SelectedIngredientLabel.java b/src/main/java/mi/hdm/components/SelectedIngredientLabel.java
new file mode 100644
index 0000000..137dd2e
--- /dev/null
+++ b/src/main/java/mi/hdm/components/SelectedIngredientLabel.java
@@ -0,0 +1,41 @@
+package mi.hdm.components;
+
+import javafx.scene.control.Label;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.HBox;
+import mi.hdm.recipes.RecipeComponent;
+
+public class SelectedIngredientLabel extends HBox {
+    private final RecipeComponent component;
+    private final TextField amountTextField;
+    private Integer amount = null;
+
+    public SelectedIngredientLabel(RecipeComponent component) {
+        super();
+        this.component = component;
+        amountTextField = new TextField("Enter amount");
+        amountTextField.setOnAction(e -> validateAmountInput());
+        amountTextField.setPrefWidth(75.0);
+        Label componentNameLabel = new Label(component.getName());
+
+        getChildren().addAll(amountTextField, componentNameLabel);
+    }
+
+    private void validateAmountInput() {
+        String text = amountTextField.getText().trim();
+        try {
+            amount = Integer.parseInt(text);
+        } catch (NumberFormatException e) {
+            amountTextField.clear();
+            amount = null;
+        }
+    }
+
+    public Integer getAmount() {
+        return amount;
+    }
+
+    public RecipeComponent getComponent() {
+        return component;
+    }
+}
diff --git a/src/main/java/mi/hdm/controllers/MainPageController.java b/src/main/java/mi/hdm/controllers/MainPageController.java
index a524cdd..3f1a667 100644
--- a/src/main/java/mi/hdm/controllers/MainPageController.java
+++ b/src/main/java/mi/hdm/controllers/MainPageController.java
@@ -48,6 +48,7 @@ public class MainPageController extends BaseController {
     @FXML
     public void handleAddRecipe() {
         log.info("User is trying to add recipe!");
+        changeScene(View.RECIPE_CREATOR);
     }
 
     private void mapRecipes(List<Recipe> recipes) {
diff --git a/src/main/java/mi/hdm/controllers/RecipeCreatorController.java b/src/main/java/mi/hdm/controllers/RecipeCreatorController.java
index 72a3989..a3f5047 100644
--- a/src/main/java/mi/hdm/controllers/RecipeCreatorController.java
+++ b/src/main/java/mi/hdm/controllers/RecipeCreatorController.java
@@ -1,4 +1,118 @@
 package mi.hdm.controllers;
 
-public class RecipeCreatorController {
+import javafx.fxml.FXML;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.SplitPane;
+import javafx.scene.control.TextArea;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import mi.hdm.components.CategoryCheckBox;
+import mi.hdm.components.CategoryPreviewLabel;
+import mi.hdm.components.IngredientSearchResultLabel;
+import mi.hdm.recipes.*;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class RecipeCreatorController extends BaseController {
+    private final RecipeManager recipeManager;
+    private final IngredientManager ingredientManager;
+    private final CategoryManager categoryManager;
+    private final RecipeSearch recipeSearch;
+
+    private final List<Category> selectedCategories;
+    private final Map<Integer, RecipeComponent> selectedIngredients;
+
+    private static final Logger log = LogManager.getLogger(RecipeCreatorController.class);
+
+    @FXML
+    private AnchorPane parent;
+    @FXML
+    private SplitPane topSplitPane;
+    @FXML
+    private TextField name;
+    @FXML
+    private TextArea description;
+    @FXML
+    private TextField prepTime;
+    @FXML
+    private HBox categories;
+    @FXML
+    private FlowPane allCategories;
+    @FXML
+    private TextField ingredientSearch;
+    @FXML
+    private ScrollPane searchResultsPane;
+    @FXML
+    private TextArea preparation;
+    @FXML
+    private ScrollPane ingredientsScrollBar;
+
+    public RecipeCreatorController(RecipeManager recipeManager, IngredientManager ingredientManager, CategoryManager categoryManager) {
+        this.recipeManager = recipeManager;
+        this.ingredientManager = ingredientManager;
+        this.categoryManager = categoryManager;
+        selectedCategories = new ArrayList<>();
+        selectedIngredients = new HashMap<>();
+        recipeSearch = new RecipeSearch(recipeManager, ingredientManager);
+    }
+
+    @FXML
+    public void initialize() {
+        //TODO: this needs a "go back" button at the top (not the header)
+        mapCategories();
+        ingredientSearch.textProperty().addListener(((e) -> searchIngredients()));
+    }
+
+    private void mapCategories() {
+        for (final Category category : categoryManager.getAllCategories()) {
+            CategoryCheckBox checkbox = new CategoryCheckBox(category);
+            checkbox.setOnAction(e -> updateSelectedCategories(checkbox));
+            allCategories.getChildren().add(checkbox);
+        }
+    }
+
+    private void updateSelectedCategories(CategoryCheckBox checkbox) {
+        if (checkbox.isSelected()) {
+            selectedCategories.add(checkbox.getAssociatedCategory());
+            log.debug("Added '{}' to list of selected categories", checkbox.getAssociatedCategory().getName());
+        } else {
+            selectedCategories.remove(checkbox.getAssociatedCategory());
+            log.debug("Removed '{}' from list of selected categories", checkbox.getAssociatedCategory().getName());
+        }
+        drawSelectedCategories();
+    }
+
+    private void drawSelectedCategories() {
+        log.debug("Drawing selected categories");
+        categories.getChildren().clear();
+        for (final Category category : selectedCategories) {
+            CategoryPreviewLabel label = new CategoryPreviewLabel(category);
+            categories.getChildren().add(label);
+        }
+    }
+
+    public void searchIngredients() {
+        String input = ingredientSearch.getText();
+        List<RecipeComponent> results = recipeSearch.searchThroughNames(input);
+        System.out.println("Search produced " + results.size() + " results.");
+        drawIngredientSearchResults(results);
+    }
+
+    private void drawIngredientSearchResults(List<RecipeComponent> results) {
+        VBox resultContainer = new VBox();
+        searchResultsPane.setContent(resultContainer);
+        results.stream()
+                .limit(100)
+                .forEach(
+                        r -> resultContainer.getChildren().add(new IngredientSearchResultLabel(r)) //TODO: add event listener to these labels
+                );
+    }
 }
diff --git a/src/main/java/mi/hdm/controllers/View.java b/src/main/java/mi/hdm/controllers/View.java
index 55a9f44..286a981 100644
--- a/src/main/java/mi/hdm/controllers/View.java
+++ b/src/main/java/mi/hdm/controllers/View.java
@@ -15,7 +15,8 @@ public enum View {
     MAIN("/fxml/Tasty_Pages_Main_Page.fxml", "Tasty Pages"),
     RECIPE_VIEW("/fxml/recipe-view.fxml", "Tasty Pages - Recipe View"),
     SHOPPINGLIST_VIEW("/fxml/shoppingList-view.fxml", "Tasty Pages - Shopping List"),
-    MEALPLAN_VIEW("/fxml/Meal_Plan.fxml", "Tasty Pages - Meal Plan");
+    MEALPLAN_VIEW("/fxml/Meal_Plan.fxml", "Tasty Pages - Meal Plan"),
+    RECIPE_CREATOR("/fxml/recipe-creator.fxml", "Tasty Pages - Recipe Creator");
 
     private static final Logger log = LogManager.getLogger(View.class);
 
@@ -54,6 +55,10 @@ public enum View {
             case MEALPLAN_VIEW -> loader.setControllerFactory((callback) ->
                     new MealPlanController(model.getMealPlan())
             );
+
+            case RECIPE_CREATOR -> loader.setControllerFactory((callback) ->
+                    new RecipeCreatorController(model.getRecipeManager(), model.getIngredientManager(), model.getCategoryManager())
+            );
         }
         log.debug("Set controller factory for {}", windowTitle);
 
diff --git a/src/main/java/mi/hdm/recipes/Category.java b/src/main/java/mi/hdm/recipes/Category.java
index f5a7fd3..71dd4fd 100644
--- a/src/main/java/mi/hdm/recipes/Category.java
+++ b/src/main/java/mi/hdm/recipes/Category.java
@@ -45,7 +45,7 @@ public class Category {
 
     @Override
     public String toString() {
-        return String.format("Category %s with color code %s", name, colourCode);
+        return String.format("Category '%s' with color code %s", name, colourCode);
     }
 
     public int getCode() {
diff --git a/src/main/java/mi/hdm/recipes/RecipeSearch.java b/src/main/java/mi/hdm/recipes/RecipeSearch.java
index 45f3c5e..badd7fd 100644
--- a/src/main/java/mi/hdm/recipes/RecipeSearch.java
+++ b/src/main/java/mi/hdm/recipes/RecipeSearch.java
@@ -73,4 +73,19 @@ public class RecipeSearch {
         }
         return result;
     }
+
+    public List<RecipeComponent> searchThroughNames(String query) {
+        //TODO: parallel stream
+        if (query == null || query.isBlank()) return List.of();
+        List<RecipeComponent> result = new ArrayList<>();
+        result.addAll(ingredientManager.getAllIngredients().values()
+                .stream()
+                .filter(c -> c.getName().toLowerCase().contains(query.toLowerCase().trim()))
+                .toList());
+        result.addAll(recipeManager.getRecipes()
+                .stream()
+                .filter(c -> c.getName().toLowerCase().contains(query.toLowerCase().trim()))
+                .toList());
+        return result;
+    }
 }
diff --git a/src/main/resources/fxml/recipe-creator.fxml b/src/main/resources/fxml/recipe-creator.fxml
index ad7005f..3e22c26 100644
--- a/src/main/resources/fxml/recipe-creator.fxml
+++ b/src/main/resources/fxml/recipe-creator.fxml
@@ -1,31 +1,91 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
-<?import javafx.geometry.Insets?>
-<?import javafx.scene.control.Label?>
-<?import javafx.scene.control.SplitPane?>
-<?import javafx.scene.control.TextField?>
+<?import javafx.geometry.*?>
+<?import javafx.scene.control.*?>
 <?import javafx.scene.layout.*?>
-<AnchorPane prefHeight="800" prefWidth="1200.0" xmlns="http://javafx.com/javafx/17.0.2-ea"
+<AnchorPane fx:id="parent" prefHeight="800" prefWidth="1200.0" xmlns="http://javafx.com/javafx/17.0.2-ea"
             xmlns:fx="http://javafx.com/fxml/1" fx:controller="mi.hdm.controllers.RecipeCreatorController">
-    <children>
-        <SplitPane dividerPositions="0.5" prefHeight="160.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0"
-                   AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="40.0">
-            <items>
-                <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
-                    <children>
-                        <VBox prefHeight="200.0" prefWidth="100.0">
-                            <children>
-                                <Label text="Recipe name"/>
-                                <TextField/>
-                            </children>
-                        </VBox>
-                    </children>
-                </AnchorPane>
-                <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0"/>
-            </items>
-        </SplitPane>
-    </children>
     <padding>
         <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
     </padding>
+    <SplitPane fx:id="topSplitPane" dividerPositions="0.5" prefHeight="160.0" prefWidth="200.0"
+               AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"
+               AnchorPane.topAnchor="40.0">
+        <AnchorPane minHeight="0.0" minWidth="300.0" prefHeight="160.0" prefWidth="100.0">
+            <padding>
+                <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
+            </padding>
+            <VBox prefHeight="200.0" prefWidth="100.0" spacing="5.0" AnchorPane.bottomAnchor="0.0"
+                  AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
+                <Label text="Recipe name"/>
+                <TextField fx:id="name" promptText="Enter a name for your recipe">
+                    <VBox.margin>
+                        <Insets bottom="5.0"/>
+                    </VBox.margin>
+                </TextField>
+                <Label text="Recipe description"/>
+                <TextArea fx:id="description" prefHeight="104.0" prefWidth="566.0"
+                          promptText="Enter a description for your recipe" wrapText="true">
+                    <VBox.margin>
+                        <Insets bottom="5.0"/>
+                    </VBox.margin>
+                </TextArea>
+                <Label text="Preparation time"/>
+                <TextField fx:id="prepTime" promptText="How much time does it take to prepare this recipe in minutes?">
+                    <VBox.margin>
+                        <Insets bottom="5.0"/>
+                    </VBox.margin>
+                </TextField>
+                <Label text="Selected categories"/>
+                <HBox fx:id="categories" minHeight="20.0" style="-fx-background-color: DEDEDE;">
+                    <padding>
+                        <Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
+                    </padding>
+                    <VBox.margin>
+                        <Insets bottom="5.0"/>
+                    </VBox.margin>
+                </HBox>
+                <Label text="Recipe description"/>
+                <TextArea fx:id="preparation" prefHeight="104.0" prefWidth="566.0"
+                          promptText="How is your recipe prepared? Add new lines to separate instructions"
+                          wrapText="true">
+                    <VBox.margin>
+                        <Insets bottom="5.0"/>
+                    </VBox.margin>
+                </TextArea>
+                <Label text="Ingredients"/>
+                <ScrollPane prefHeight="200.0" prefWidth="200.0" fx:id="ingredientsScrollBar"/>
+                <Button mnemonicParsing="false" text="Create Recipe">
+                    <VBox.margin>
+                        <Insets top="10.0"/>
+                    </VBox.margin>
+                </Button>
+            </VBox>
+        </AnchorPane>
+
+        <AnchorPane minHeight="0.0" minWidth="300.0" prefHeight="160.0" prefWidth="100.0">
+            <padding>
+                <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
+            </padding>
+            <VBox prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
+                  AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
+                <Label text="Select categories"/>
+                <FlowPane fx:id="allCategories" prefHeight="129.0" prefWidth="566.0"/>
+                <Separator prefWidth="200.0"/>
+                <Label text="Search for ingredients">
+                    <VBox.margin>
+                        <Insets top="10.0"/>
+                    </VBox.margin>
+                </Label>
+                <TextField fx:id="ingredientSearch" onInputMethodTextChanged="#searchIngredients"
+                           promptText="Type something...">
+                    <VBox.margin>
+                        <Insets bottom="5.0" top="5.0"/>
+                    </VBox.margin>
+                </TextField>
+                <ScrollPane fx:id="searchResultsPane" fitToHeight="true" maxHeight="1.7976931348623157E308"
+                            prefHeight="509.0" prefWidth="566.0"/>
+            </VBox>
+        </AnchorPane>
+    </SplitPane>
 </AnchorPane>
-- 
GitLab