From 6b851767e781b48af69748fe1b4effc24ed95e51 Mon Sep 17 00:00:00 2001 From: Steven Liu Date: Tue, 13 Jan 2026 16:37:57 +0100 Subject: [PATCH 1/5] chore: integrate changes from newest main --- client/src/main/java/client/MyModule.java | 5 +- .../client/scenes/FoodpalApplicationCtrl.java | 8 +- .../scenes/Ingredient/IngredientListCtrl.java | 148 ++++++++++++++++++ .../nutrition/NutritionDetailsCtrl.java | 24 +++ .../scenes/nutrition/NutritionViewCtrl.java | 61 -------- .../IngredientList.fxml} | 2 +- .../scenes/nutrition/NutritionDetails.fxml | 44 +++--- .../scenes/nutrition/NutritionView.fxml | 6 +- .../scenes/recipe/RecipeDetailView.fxml | 3 +- ...entList.fxml => RecipeIngredientList.fxml} | 0 10 files changed, 205 insertions(+), 96 deletions(-) create mode 100644 client/src/main/java/client/scenes/Ingredient/IngredientListCtrl.java rename client/src/main/resources/client/scenes/{recipe/IngredientsPopup.fxml => nutrition/IngredientList.fxml} (93%) rename client/src/main/resources/client/scenes/recipe/{IngredientList.fxml => RecipeIngredientList.fxml} (100%) 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 @@ - + Date: Thu, 15 Jan 2026 17:08:45 +0100 Subject: [PATCH 2/5] feat(client/utils): create update PATCH util function --- .../main/java/client/utils/ServerUtils.java | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/client/src/main/java/client/utils/ServerUtils.java b/client/src/main/java/client/utils/ServerUtils.java index 113ed01..606afd5 100644 --- a/client/src/main/java/client/utils/ServerUtils.java +++ b/client/src/main/java/client/utils/ServerUtils.java @@ -216,9 +216,13 @@ public class ServerUtils { updateRecipe(recipe); } - - // how many ingredients are getting used in recipes - + /** + * Gets the amount of recipes this ingredient is being used in. + * @param ingredientId The queried ingredient's ID. + * @return The amount of recipes the ingredient is used in. + * @throws IOException if server query failed. + * @throws InterruptedException if operation is interrupted. + */ public long getIngredientUsage(long ingredientId) throws IOException, InterruptedException { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(SERVER + "/ingredients/" + ingredientId + "/usage")) @@ -292,6 +296,22 @@ public class ServerUtils { return objectMapper.readValue(response.body(), Ingredient.class); } + public Ingredient updateIngredient(Ingredient newIngredient) throws IOException, InterruptedException { + logger.info("PATCH ingredient with id: " + newIngredient.getId()); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(SERVER + "/ingredients/" + newIngredient.getId())) + .header("Content-Type", "application/json") + .method( + "PATCH", + HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(newIngredient))) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != statusOK) { + throw new IOException("Failed to update ingredient with id: " + newIngredient.getId() + " body: " + response.body()); + } + return objectMapper.readValue(response.body(), Ingredient.class); + } + From 1bfc103eccf2d446e11a5ca40e645a641ae5d9b8 Mon Sep 17 00:00:00 2001 From: Steven Liu Date: Thu, 15 Jan 2026 17:09:39 +0100 Subject: [PATCH 3/5] feat(client/nutrition): updated nutrition UI modelling Uses "idiomatic JavaFX" to model automatic update propagation by object view models. --- .../Ingredient/IngredientViewModel.java | 71 +++++++++++++++ .../IllegalInputFormatException.java | 7 ++ .../scenes/Ingredient/IngredientListCtrl.java | 5 ++ .../nutrition/NutritionDetailsCtrl.java | 88 +++++++++++++++++-- .../scenes/nutrition/NutritionDetails.fxml | 21 ++--- 5 files changed, 173 insertions(+), 19 deletions(-) create mode 100644 client/src/main/java/client/Ingredient/IngredientViewModel.java create mode 100644 client/src/main/java/client/exception/IllegalInputFormatException.java diff --git a/client/src/main/java/client/Ingredient/IngredientViewModel.java b/client/src/main/java/client/Ingredient/IngredientViewModel.java new file mode 100644 index 0000000..90499a8 --- /dev/null +++ b/client/src/main/java/client/Ingredient/IngredientViewModel.java @@ -0,0 +1,71 @@ +package client.Ingredient; + +import commons.Ingredient; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.DoubleBinding; +import javafx.beans.property.*; + +public class IngredientViewModel { + private final LongProperty id = new SimpleLongProperty(); + private final StringProperty name = new SimpleStringProperty(); + private final DoubleProperty protein = new SimpleDoubleProperty(); + private final DoubleProperty fat = new SimpleDoubleProperty(); + private final DoubleProperty carbs = new SimpleDoubleProperty(); + + private final ReadOnlyDoubleWrapper kcal = new ReadOnlyDoubleWrapper(); + public IngredientViewModel() { + DoubleBinding computeKcal = Bindings.createDoubleBinding( + () -> toIngredient().kcalPer100g(), + protein, carbs, fat + ); + + // bind the read-only wrapper to the binding + kcal.bind(computeKcal); + + } + + public IngredientViewModel(Ingredient ing) { + updateFrom(ing); + } + + public void updateFrom(Ingredient ing) { + if (ing == null) return; + id.set(ing.getId()); + name.set(ing.getName()); + protein.set(ing.getProteinPer100g()); + fat.set(ing.getFatPer100g()); + carbs.set(ing.getCarbsPer100g()); + } + + // property getters + public LongProperty idProperty() { + return id; + } + public StringProperty nameProperty() { + return name; + } + public DoubleProperty proteinProperty() { + return protein; + } + public DoubleProperty fatProperty() { + return fat; + } + public DoubleProperty carbsProperty() { + return carbs; + } + + public Ingredient toIngredient() { + Ingredient i = new Ingredient(); + i.setId(id.get()); + i.setName(name.get()); + i.setProteinPer100g(protein.get()); + i.setFatPer100g(fat.get()); + i.setCarbsPer100g(carbs.get()); + return i; + } + public ReadOnlyDoubleProperty kcalProperty() { + return kcal.getReadOnlyProperty(); } + public double getKcal() { + return kcal.get(); } +} + diff --git a/client/src/main/java/client/exception/IllegalInputFormatException.java b/client/src/main/java/client/exception/IllegalInputFormatException.java new file mode 100644 index 0000000..487b523 --- /dev/null +++ b/client/src/main/java/client/exception/IllegalInputFormatException.java @@ -0,0 +1,7 @@ +package client.exception; + +public class IllegalInputFormatException extends RuntimeException { + public IllegalInputFormatException(String message) { + super(message); + } +} diff --git a/client/src/main/java/client/scenes/Ingredient/IngredientListCtrl.java b/client/src/main/java/client/scenes/Ingredient/IngredientListCtrl.java index 68f5e9a..b63df27 100644 --- a/client/src/main/java/client/scenes/Ingredient/IngredientListCtrl.java +++ b/client/src/main/java/client/scenes/Ingredient/IngredientListCtrl.java @@ -14,6 +14,7 @@ import javafx.stage.Stage; import java.io.IOException; import java.util.List; import java.util.Optional; +import java.util.logging.Logger; //TODO and check for capital letter milk and MILK are seen as different @@ -21,6 +22,9 @@ import java.util.Optional; public class IngredientListCtrl { private final ServerUtils server; + private final Logger logger = Logger.getLogger(IngredientListCtrl.class.getName()); + @FXML + private ListView ingredientListView; @FXML private NutritionDetailsCtrl nutritionDetailsCtrl; @FXML @@ -52,6 +56,7 @@ public class IngredientListCtrl { if (newValue == null) { return; } + logger.info("Selected ingredient " + newValue.getName() + ", propagating to Nutrition Details controller..."); nutritionDetailsCtrl.setItem(newValue); }); diff --git a/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java b/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java index cd8046f..8232b23 100644 --- a/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java +++ b/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java @@ -1,18 +1,43 @@ package client.scenes.nutrition; +import client.Ingredient.IngredientViewModel; +import client.exception.IllegalInputFormatException; import client.utils.LocaleAware; import client.utils.LocaleManager; +import client.utils.ServerUtils; import com.google.inject.Inject; import commons.Ingredient; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.property.SimpleObjectProperty; +import javafx.event.ActionEvent; +import javafx.event.EventType; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.GridPane; import javafx.scene.layout.VBox; +import javafx.util.converter.NumberStringConverter; + +import java.io.IOException; +import java.util.IllegalFormatConversionException; +import java.util.IllegalFormatException; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Logger; public class NutritionDetailsCtrl implements LocaleAware { + @FXML + public GridPane nutritionValueContainer; + private boolean visible; private final LocaleManager manager; - private Ingredient ingredient; + private final ServerUtils server; + private final SimpleObjectProperty ingredient = + new SimpleObjectProperty<>(new IngredientViewModel()); + private final Logger logger = Logger.getLogger(this.getClass().getName()); public Label ingredientName; public Label fatInputLabel; @@ -30,10 +55,45 @@ public class NutritionDetailsCtrl implements LocaleAware { @Inject public NutritionDetailsCtrl( - LocaleManager manager + LocaleManager manager, + ServerUtils server ) { this.manager = manager; + this.server = server; } + + @Override + public void initializeComponents() { + Platform.runLater(() -> { + IngredientViewModel vm = this.ingredient.get(); + this.ingredientName.textProperty().bind(vm.nameProperty()); + this.fatInputElement.textProperty().bindBidirectional(vm.fatProperty(), new NumberStringConverter()); + this.proteinInputElement.textProperty().bindBidirectional(vm.proteinProperty(), new NumberStringConverter()); + this.carbInputElement.textProperty().bindBidirectional(vm.carbsProperty(), new NumberStringConverter()); + this.estimatedKcalLabel.textProperty().bind(Bindings.createStringBinding( + () -> String.format("Estimated energy value: %.1f", vm.getKcal()), vm.kcalProperty() + )); + }); + this.nutritionValueContainer.addEventHandler(KeyEvent.KEY_RELEASED, event -> { + if (event.getCode() != KeyCode.ENTER) { + return; + } + try { + Ingredient newIngredient = server.updateIngredient(updateIngredient()); + Platform.runLater(() -> { + this.ingredient.get().updateFrom(newIngredient); + logger.info("Updated ingredient to " + newIngredient); + }); + } catch (IllegalInputFormatException e) { + e.printStackTrace(); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + } + @Override public void updateText() { @@ -47,16 +107,30 @@ public class NutritionDetailsCtrl implements LocaleAware { 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())); + this.ingredient.get().updateFrom(ingredient); setVisible(true); } + private Ingredient updateIngredient() throws IllegalInputFormatException { + Ingredient current = this.ingredient.get().toIngredient(); + try { + double f = Double.parseDouble(this.fatInputElement.getText()); + double p = Double.parseDouble(this.proteinInputElement.getText()); + double c = Double.parseDouble(this.carbInputElement.getText()); + current.setCarbsPer100g(c); + current.setProteinPer100g(p); + current.setFatPer100g(f); + return current; + } catch (NumberFormatException e) { + throw new IllegalInputFormatException("Invalid F/P/C value"); + } + } @Override public LocaleManager getLocaleManager() { return manager; } + public void handleNutritionSaveClick(ActionEvent actionEvent) throws IOException, InterruptedException { + Ingredient newIngredient = updateIngredient(); + this.ingredient.get().updateFrom(server.updateIngredient(newIngredient)); + } } diff --git a/client/src/main/resources/client/scenes/nutrition/NutritionDetails.fxml b/client/src/main/resources/client/scenes/nutrition/NutritionDetails.fxml index 43beb7e..542058d 100644 --- a/client/src/main/resources/client/scenes/nutrition/NutritionDetails.fxml +++ b/client/src/main/resources/client/scenes/nutrition/NutritionDetails.fxml @@ -13,18 +13,15 @@ fx:id="nutritionDetails" visible="false"> From 9f7f32beffdf58fe2dac4465a28bc69c9d97a44b Mon Sep 17 00:00:00 2001 From: Steven Liu Date: Thu, 15 Jan 2026 17:10:01 +0100 Subject: [PATCH 4/5] fix(commons/ingredient): add missing setters --- .../client/Ingredient/IngredientViewModel.java | 9 ++++++++- .../scenes/Ingredient/IngredientListCtrl.java | 2 -- .../scenes/nutrition/NutritionDetailsCtrl.java | 5 ----- commons/src/main/java/commons/Ingredient.java | 15 +++++++++++++-- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/client/src/main/java/client/Ingredient/IngredientViewModel.java b/client/src/main/java/client/Ingredient/IngredientViewModel.java index 90499a8..799c69f 100644 --- a/client/src/main/java/client/Ingredient/IngredientViewModel.java +++ b/client/src/main/java/client/Ingredient/IngredientViewModel.java @@ -3,7 +3,14 @@ package client.Ingredient; import commons.Ingredient; import javafx.beans.binding.Bindings; import javafx.beans.binding.DoubleBinding; -import javafx.beans.property.*; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.LongProperty; +import javafx.beans.property.ReadOnlyDoubleProperty; +import javafx.beans.property.ReadOnlyDoubleWrapper; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleLongProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; public class IngredientViewModel { private final LongProperty id = new SimpleLongProperty(); diff --git a/client/src/main/java/client/scenes/Ingredient/IngredientListCtrl.java b/client/src/main/java/client/scenes/Ingredient/IngredientListCtrl.java index b63df27..4d223c8 100644 --- a/client/src/main/java/client/scenes/Ingredient/IngredientListCtrl.java +++ b/client/src/main/java/client/scenes/Ingredient/IngredientListCtrl.java @@ -27,8 +27,6 @@ public class IngredientListCtrl { private ListView ingredientListView; @FXML private NutritionDetailsCtrl nutritionDetailsCtrl; - @FXML - private ListView ingredientListView; @Inject public IngredientListCtrl( diff --git a/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java b/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java index 8232b23..25b7bfb 100644 --- a/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java +++ b/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java @@ -11,7 +11,6 @@ import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleObjectProperty; import javafx.event.ActionEvent; -import javafx.event.EventType; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.control.TextField; @@ -22,10 +21,6 @@ import javafx.scene.layout.VBox; import javafx.util.converter.NumberStringConverter; import java.io.IOException; -import java.util.IllegalFormatConversionException; -import java.util.IllegalFormatException; -import java.util.Optional; -import java.util.Set; import java.util.logging.Logger; public class NutritionDetailsCtrl implements LocaleAware { diff --git a/commons/src/main/java/commons/Ingredient.java b/commons/src/main/java/commons/Ingredient.java index c824912..a195dfc 100644 --- a/commons/src/main/java/commons/Ingredient.java +++ b/commons/src/main/java/commons/Ingredient.java @@ -84,10 +84,21 @@ public class Ingredient { public double getCarbsPer100g() { return carbsPer100g; } - public Long getId() { return id; } + public void setCarbsPer100g(double carbsPer100g) { + this.carbsPer100g = carbsPer100g; + } + + public void setFatPer100g(double fatPer100g) { + this.fatPer100g = fatPer100g; + } + + public void setProteinPer100g(double proteinPer100g) { + this.proteinPer100g = proteinPer100g; + } + public String getName() { return name; @@ -101,7 +112,7 @@ public class Ingredient { public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; Ingredient that = (Ingredient) o; - return id == that.id && Double.compare(proteinPer100g, that.proteinPer100g) == 0 && Double.compare(fatPer100g, that.fatPer100g) == 0 && Double.compare(carbsPer100g, that.carbsPer100g) == 0 && Objects.equals(name, that.name); + return Objects.equals(id, that.id) && Double.compare(proteinPer100g, that.proteinPer100g) == 0 && Double.compare(fatPer100g, that.fatPer100g) == 0 && Double.compare(carbsPer100g, that.carbsPer100g) == 0 && Objects.equals(name, that.name); } @Override From c5f338bf7443f15e5cdc412904f028bd5f992743 Mon Sep 17 00:00:00 2001 From: Steven Liu Date: Thu, 15 Jan 2026 17:24:19 +0100 Subject: [PATCH 5/5] chore: cleanup --- .../client/Ingredient/IngredientController.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/client/src/main/java/client/Ingredient/IngredientController.java b/client/src/main/java/client/Ingredient/IngredientController.java index 14d4197..429fecd 100644 --- a/client/src/main/java/client/Ingredient/IngredientController.java +++ b/client/src/main/java/client/Ingredient/IngredientController.java @@ -111,14 +111,13 @@ public class IngredientController { public static class IngredientUsageResponse { private long ingredientId; private long usedInRecipes; - - public long getIngredientId() { - return ingredientId; - } - - public void setIngredientId(long ingredientId) { - this.ingredientId = ingredientId; - } +// public long getIngredientId() { +// return ingredientId; +// } +// +// public void setIngredientId(long ingredientId) { +// this.ingredientId = ingredientId; +// } public long getUsedInRecipes() { return usedInRecipes;