diff --git a/client/src/main/java/client/MyModule.java b/client/src/main/java/client/MyModule.java index 668c62d..661b2df 100644 --- a/client/src/main/java/client/MyModule.java +++ b/client/src/main/java/client/MyModule.java @@ -17,6 +17,8 @@ package client; import client.scenes.FoodpalApplicationCtrl; import client.scenes.SearchBarCtrl; +import client.scenes.nutrition.NutritionDetailsCtrl; +import client.scenes.nutrition.NutritionViewCtrl; import client.scenes.recipe.IngredientListCtrl; import client.scenes.recipe.RecipeStepListCtrl; import client.utils.ConfigService; @@ -47,7 +49,8 @@ public class MyModule implements Module { binder.bind(LocaleManager.class).in(Scopes.SINGLETON); binder.bind(ServerUtils.class).in(Scopes.SINGLETON); binder.bind(WebSocketUtils.class).in(Scopes.SINGLETON); - + binder.bind(NutritionDetailsCtrl.class).in(Scopes.SINGLETON); + binder.bind(NutritionViewCtrl.class).in(Scopes.SINGLETON); binder.bind(ConfigService.class).toInstance(new ConfigService(Path.of("config.json"))); binder.bind(new TypeLiteral>() {}).toInstance( new WebSocketDataService<>() diff --git a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java index f2f658d..93ec852 100644 --- a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java +++ b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java @@ -10,7 +10,7 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import client.exception.InvalidModificationException; -import client.scenes.recipe.IngredientsPopupCtrl; +import client.scenes.nutrition.NutritionViewCtrl; import client.scenes.recipe.RecipeDetailCtrl; import client.utils.Config; @@ -524,14 +524,14 @@ public class FoodpalApplicationCtrl implements LocaleAware { private void openIngredientsPopup() { try { var pair = client.UI.getFXML().load( - IngredientsPopupCtrl.class, - "client", "scenes", "recipe", "IngredientsPopup.fxml" + NutritionViewCtrl.class, + "client", "scenes", "nutrition", "NutritionView.fxml" ); var root = pair.getValue(); var stage = new javafx.stage.Stage(); - stage.setTitle("Ingredients"); + stage.setTitle("Nutrition values view"); stage.initModality(javafx.stage.Modality.APPLICATION_MODAL); stage.setScene(new javafx.scene.Scene(root)); stage.showAndWait(); diff --git a/client/src/main/java/client/scenes/Ingredient/IngredientListCtrl.java b/client/src/main/java/client/scenes/Ingredient/IngredientListCtrl.java new file mode 100644 index 0000000..68f5e9a --- /dev/null +++ b/client/src/main/java/client/scenes/Ingredient/IngredientListCtrl.java @@ -0,0 +1,148 @@ +package client.scenes.Ingredient; + +import client.scenes.nutrition.NutritionDetailsCtrl; +import client.utils.ServerUtils; +import commons.Ingredient; +import jakarta.inject.Inject; +import javafx.fxml.FXML; +import javafx.scene.control.Alert; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.TextInputDialog; +import javafx.stage.Stage; + +import java.io.IOException; +import java.util.List; +import java.util.Optional; + +//TODO and check for capital letter milk and MILK are seen as different + + +public class IngredientListCtrl { + + private final ServerUtils server; + @FXML + private NutritionDetailsCtrl nutritionDetailsCtrl; + @FXML + private ListView ingredientListView; + + @Inject + public IngredientListCtrl( + ServerUtils server, + NutritionDetailsCtrl nutritionDetailsCtrl + ) { + this.server = server; + this.nutritionDetailsCtrl = nutritionDetailsCtrl; + } + + @FXML + public void initialize() { + ingredientListView.setCellFactory(list -> new ListCell<>() { + @Override + protected void updateItem(Ingredient item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(null); + } else { + setText(item.getName()); + } + } + }); + ingredientListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if (newValue == null) { + return; + } + nutritionDetailsCtrl.setItem(newValue); + }); + + refresh(); + } + @FXML + private void addIngredient() { + TextInputDialog dialog = new TextInputDialog(); + dialog.setTitle("Add Ingredient"); + dialog.setHeaderText("Create a new ingredient"); + dialog.setContentText("Name:"); + + Optional result = dialog.showAndWait(); + if (result.isEmpty()) { + return; + } + + String name = result.get().trim(); + if (name.isEmpty()) { + showError("Ingredient name cannot be empty."); + return; + } + + try { + server.createIngredient(name); // calls POST /api/ingredients + refresh(); // reload list from server + } catch (IOException | InterruptedException e) { + showError("Failed to create ingredient: " + e.getMessage()); + } + } + + + @FXML + private void refresh() { + try { + List ingredients = server.getIngredients(); + ingredientListView.getItems().setAll(ingredients); + } catch (IOException | InterruptedException e) { + showError("Failed to load ingredients: " + e.getMessage()); + } + } + + @FXML + private void deleteSelected() { + Ingredient selected = ingredientListView.getSelectionModel().getSelectedItem(); + if (selected == null) { + showError("No ingredient selected."); + return; + } + + try { + long usageCount = server.getIngredientUsage(selected.getId()); + if (usageCount > 0) { + boolean proceed = confirmDeleteUsed(selected.getName(), usageCount); + if (!proceed) { + return; + } + } + + server.deleteIngredient(selected.getId()); + refresh(); + } catch (IOException | InterruptedException e) { + showError("Failed to delete ingredient: " + e.getMessage()); + } + } + + @FXML + private void close() { + Stage stage = (Stage) ingredientListView.getScene().getWindow(); + stage.close(); + } + + private boolean confirmDeleteUsed(String name, long usedInRecipes) { + Alert alert = new Alert(Alert.AlertType.WARNING); + alert.setTitle("Warning"); + alert.setHeaderText("Ingredient in use"); + alert.setContentText("Ingredient '" + name + "' is used in " + usedInRecipes + + " recipe(s). Delete anyway?"); + + var delete = new javafx.scene.control.ButtonType("Delete Anyway"); + var cancel = new javafx.scene.control.ButtonType("Cancel"); + alert.getButtonTypes().setAll(delete, cancel); + + return alert.showAndWait().orElse(cancel) == delete; + } + + private void showError(String msg) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("Error"); + alert.setHeaderText(null); + alert.setContentText(msg); + alert.showAndWait(); + } +} diff --git a/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java b/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java index 9a60b4a..cd8046f 100644 --- a/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java +++ b/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java @@ -3,11 +3,16 @@ package client.scenes.nutrition; import client.utils.LocaleAware; import client.utils.LocaleManager; import com.google.inject.Inject; +import commons.Ingredient; +import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.control.TextField; +import javafx.scene.layout.VBox; public class NutritionDetailsCtrl implements LocaleAware { + private boolean visible; private final LocaleManager manager; + private Ingredient ingredient; public Label ingredientName; public Label fatInputLabel; @@ -20,6 +25,9 @@ public class NutritionDetailsCtrl implements LocaleAware { public TextField proteinInputElement; public TextField carbInputElement; + @FXML + public VBox nutritionDetails; + @Inject public NutritionDetailsCtrl( LocaleManager manager @@ -31,6 +39,22 @@ public class NutritionDetailsCtrl implements LocaleAware { } + public void setVisible(boolean isVisible) { + nutritionDetails.setVisible(isVisible); + } + public void toggleVisible() { + nutritionDetails.setVisible(!visible); + visible = !visible; + } + public void setItem(Ingredient ingredient) { + this.ingredient = ingredient; + this.ingredientName.setText(ingredient.getName()); + this.fatInputElement.setText(Double.toString(ingredient.getFatPer100g())); + this.proteinInputElement.setText(Double.toString(ingredient.getProteinPer100g())); + this.carbInputElement.setText(Double.toString(ingredient.getCarbsPer100g())); + setVisible(true); + } + @Override public LocaleManager getLocaleManager() { return manager; diff --git a/client/src/main/java/client/scenes/nutrition/NutritionViewCtrl.java b/client/src/main/java/client/scenes/nutrition/NutritionViewCtrl.java index 7cac4e1..df8bdad 100644 --- a/client/src/main/java/client/scenes/nutrition/NutritionViewCtrl.java +++ b/client/src/main/java/client/scenes/nutrition/NutritionViewCtrl.java @@ -1,71 +1,10 @@ package client.scenes.nutrition; -import client.scenes.FoodpalApplicationCtrl; import com.google.inject.Inject; -import commons.Ingredient; -import commons.Recipe; -import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; -import javafx.scene.control.ListView; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Set; public class NutritionViewCtrl { - private ObservableList recipes; - private HashMap ingredientStats; - public ListView nutritionIngredientsView; - private final NutritionDetailsCtrl nutritionDetailsCtrl; - - // TODO into Ingredient class definition - // FIXME MOST LIKELY CURRENTLY BROKEN. TO BE FIXED. - /** - * Comedically verbose function to count unique appearances of an ingredient by name in each recipe. - * For each recipe: - * 1. Collect unique ingredients that appeared in that recipe. - * 2. For each unique ingredient in said recipe: - * 1. Initialize the appearance for that ingredient to 0. - * 2. For each recipe in list: - * 1. If the name of the ingredient exists in the recipe list, increment the statistic by 1. - * 2. Else maintain the same value for that statistic. - * @param recipeList The recipe list - */ - private void updateIngredientStats( - List recipeList - ) { - recipeList.forEach(recipe -> { - Set uniqueIngredients = new HashSet<>( - recipe.getIngredients().stream().map( - ingredient -> ingredient.ingredient).toList()); - nutritionIngredientsView.getItems().setAll(uniqueIngredients); - uniqueIngredients.forEach(ingredient -> { - ingredientStats.put(ingredient, 0); - recipeList.forEach(r -> - ingredientStats.put( - ingredient, - ingredientStats.get(ingredient) + ( - (r.getIngredients().contains(ingredient)) - ? 1 : 0 - ) - ) - ); - }); - }); - } @Inject public NutritionViewCtrl( - FoodpalApplicationCtrl foodpalApplicationCtrl, - NutritionDetailsCtrl nutritionDetailsCtrl ) { - this.recipes = foodpalApplicationCtrl.recipeList.getItems(); - this.recipes.addListener((ListChangeListener) _ -> { - updateIngredientStats(this.recipes); - }); - this.nutritionDetailsCtrl = nutritionDetailsCtrl; - this.nutritionIngredientsView.selectionModelProperty().addListener((observable, oldValue, newValue) -> { - - }); } } diff --git a/client/src/main/resources/client/scenes/recipe/IngredientsPopup.fxml b/client/src/main/resources/client/scenes/nutrition/IngredientList.fxml similarity index 93% rename from client/src/main/resources/client/scenes/recipe/IngredientsPopup.fxml rename to client/src/main/resources/client/scenes/nutrition/IngredientList.fxml index 8291a03..653beaa 100644 --- a/client/src/main/resources/client/scenes/recipe/IngredientsPopup.fxml +++ b/client/src/main/resources/client/scenes/nutrition/IngredientList.fxml @@ -8,7 +8,7 @@ diff --git a/client/src/main/resources/client/scenes/nutrition/NutritionDetails.fxml b/client/src/main/resources/client/scenes/nutrition/NutritionDetails.fxml index 1820eb4..43beb7e 100644 --- a/client/src/main/resources/client/scenes/nutrition/NutritionDetails.fxml +++ b/client/src/main/resources/client/scenes/nutrition/NutritionDetails.fxml @@ -7,26 +7,24 @@ - - - - - + + diff --git a/client/src/main/resources/client/scenes/nutrition/NutritionView.fxml b/client/src/main/resources/client/scenes/nutrition/NutritionView.fxml index 428694e..321bce3 100644 --- a/client/src/main/resources/client/scenes/nutrition/NutritionView.fxml +++ b/client/src/main/resources/client/scenes/nutrition/NutritionView.fxml @@ -11,9 +11,7 @@ fx:controller="client.scenes.nutrition.NutritionViewCtrl" prefHeight="400.0" prefWidth="600.0"> - - - - + + diff --git a/client/src/main/resources/client/scenes/recipe/RecipeDetailView.fxml b/client/src/main/resources/client/scenes/recipe/RecipeDetailView.fxml index 6308fe2..dd5f2ba 100644 --- a/client/src/main/resources/client/scenes/recipe/RecipeDetailView.fxml +++ b/client/src/main/resources/client/scenes/recipe/RecipeDetailView.fxml @@ -30,8 +30,7 @@ - +