From f04bbc037ead2316a9a2dc7ccabb8339b367d2f5 Mon Sep 17 00:00:00 2001 From: Steven Liu Date: Mon, 22 Dec 2025 03:00:22 +0200 Subject: [PATCH] refactor(client): client-side refactor to new modelling --- .../client/scenes/FoodpalApplicationCtrl.java | 4 +- .../scenes/nutrition/NutritionViewCtrl.java | 13 ++-- .../scenes/recipe/IngredientListCell.java | 66 +++++++++++++++---- .../scenes/recipe/IngredientListCtrl.java | 22 +++---- .../recipe/OrderedEditableListCell.java | 10 +++ .../scenes/recipe/RecipeStepListCell.java | 4 +- .../client/utils/DefaultRecipeFactory.java | 15 ----- .../client/utils/DefaultValueFactory.java | 57 ++++++++++++++++ .../main/java/client/utils/ServerUtils.java | 5 +- 9 files changed, 147 insertions(+), 49 deletions(-) delete mode 100644 client/src/main/java/client/utils/DefaultRecipeFactory.java create mode 100644 client/src/main/java/client/utils/DefaultValueFactory.java diff --git a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java index 56f6c6e..37541cb 100644 --- a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java +++ b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java @@ -11,7 +11,7 @@ import client.scenes.recipe.RecipeDetailCtrl; import client.utils.Config; import client.utils.ConfigService; -import client.utils.DefaultRecipeFactory; +import client.utils.DefaultValueFactory; import client.utils.LocaleAware; import client.utils.LocaleManager; import client.utils.ServerUtils; @@ -217,7 +217,7 @@ public class FoodpalApplicationCtrl implements LocaleAware { */ @FXML private void addRecipe() { - Recipe newRecipe = DefaultRecipeFactory.getDefaultRecipe(); // Create default recipe + Recipe newRecipe = DefaultValueFactory.getDefaultRecipe(); // Create default recipe try { newRecipe = server.addRecipe(newRecipe); // get the new recipe id refresh(); // refresh view with server recipes diff --git a/client/src/main/java/client/scenes/nutrition/NutritionViewCtrl.java b/client/src/main/java/client/scenes/nutrition/NutritionViewCtrl.java index 493db8e..7cac4e1 100644 --- a/client/src/main/java/client/scenes/nutrition/NutritionViewCtrl.java +++ b/client/src/main/java/client/scenes/nutrition/NutritionViewCtrl.java @@ -2,6 +2,7 @@ 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; @@ -14,12 +15,12 @@ import java.util.Set; public class NutritionViewCtrl { private ObservableList recipes; - private HashMap ingredientStats; - public ListView nutritionIngredientsView; + 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: @@ -29,13 +30,15 @@ public class NutritionViewCtrl { * 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 + * @param recipeList The recipe list */ private void updateIngredientStats( List recipeList ) { recipeList.forEach(recipe -> { - Set uniqueIngredients = new HashSet<>(recipe.getIngredients()); + Set uniqueIngredients = new HashSet<>( + recipe.getIngredients().stream().map( + ingredient -> ingredient.ingredient).toList()); nutritionIngredientsView.getItems().setAll(uniqueIngredients); uniqueIngredients.forEach(ingredient -> { ingredientStats.put(ingredient, 0); diff --git a/client/src/main/java/client/scenes/recipe/IngredientListCell.java b/client/src/main/java/client/scenes/recipe/IngredientListCell.java index d712302..749c635 100644 --- a/client/src/main/java/client/scenes/recipe/IngredientListCell.java +++ b/client/src/main/java/client/scenes/recipe/IngredientListCell.java @@ -1,32 +1,71 @@ package client.scenes.recipe; +import commons.FormalIngredient; +import commons.Ingredient; +import commons.RecipeIngredient; import commons.Unit; +import commons.VagueIngredient; import javafx.collections.FXCollections; import javafx.scene.control.ChoiceBox; import javafx.scene.control.TextField; import javafx.scene.input.KeyCode; import javafx.scene.layout.HBox; +import java.util.Optional; + /** - * A custom {@link OrderedEditableListCell} for displaying and editing ingredients in an - * IngredientList. Allows inline editing of ingredient names. + * A custom {@link OrderedEditableListCell} for + * displaying and editing ingredients in an IngredientList. + * Allows inline editing of ingredient names. * * @see IngredientListCtrl */ -public class IngredientListCell extends OrderedEditableListCell { +public class IngredientListCell extends OrderedEditableListCell { @Override - public void commitEdit(String newValue) { + public void commitEdit(RecipeIngredient newValue) { super.commitEdit(newValue); } + /** + * Starts an edit box on the ingredient. + * It needs an {@link HBox HBox} container to fit 3 input boxes. + * @see OrderedEditableListCell + */ @Override public void startEdit() { super.startEdit(); - TextField amountInput = new TextField("0"); + RecipeIngredient ingredient = getItem(); + + Optional templateAmount = Optional.empty(); + Optional unit = Optional.empty(); + + // Initialize the input fields with some initial data if we get a formal ingredient + if (ingredient.getClass().equals(FormalIngredient.class)) { + FormalIngredient formal = (FormalIngredient) ingredient; + templateAmount = Optional.of(formal.getAmount()); + unit = Optional.of(Unit.fromString(formal.getUnitSuffix()).orElseThrow( + () -> new RuntimeException("FormalIngredient whereas invalid unit"))); + } + // TODO initialize some other data in the case of vague ingredient + + // Initialize the input boxes + TextField amountInput = new TextField(templateAmount.map(Object::toString).orElse(null)); ChoiceBox unitChoice = new ChoiceBox<>(); - unitChoice.setItems(FXCollections.observableArrayList(Unit.GRAM, Unit.KILOGRAM)); - TextField nameInput = new TextField("ingredient"); + + // initialize the current unit if it is present + unit.ifPresent(unitChoice::setValue); + unitChoice.setItems(FXCollections.observableArrayList(Unit.GRAMME, Unit.KILOGRAMME, Unit.TONNE, Unit.INFORMAL)); + + // calls makeInputLine to create the inline edit container + HBox container = makeInputLine(ingredient, amountInput, unitChoice); + + // set the graphic to the edit box + this.setText(null); + this.setGraphic(container); + } + private HBox makeInputLine(RecipeIngredient ingredient, TextField amountInput, ChoiceBox unitChoice) { + TextField nameInput = new TextField(ingredient.ingredient.name); HBox container = new HBox(amountInput, unitChoice, nameInput); container.setOnKeyReleased(event -> { if (event.getCode() != KeyCode.ENTER) { @@ -34,15 +73,16 @@ public class IngredientListCell extends OrderedEditableListCell { } Unit unit = unitChoice.getValue(); String name = nameInput.getText(); - if (unit == null) { + if (unit == null || !unit.isFormal()) { String desc = amountInput.getText(); - commitEdit(desc + " " + name); + Ingredient newIngredient = new Ingredient(name, 0., 0., 0.); + commitEdit(new VagueIngredient(newIngredient, desc)); return; } - int amount = Integer.parseInt(amountInput.getText()); - commitEdit(amount + " " + unit + " of " + name); + double amount = Double.parseDouble(amountInput.getText()); + Ingredient newIngredient = new Ingredient(name, 0., 0., 0.); + commitEdit(new FormalIngredient(newIngredient, amount, unit.suffix)); }); - this.setText(null); - this.setGraphic(container); + return container; } } diff --git a/client/src/main/java/client/scenes/recipe/IngredientListCtrl.java b/client/src/main/java/client/scenes/recipe/IngredientListCtrl.java index 859c28a..22d61dd 100644 --- a/client/src/main/java/client/scenes/recipe/IngredientListCtrl.java +++ b/client/src/main/java/client/scenes/recipe/IngredientListCtrl.java @@ -1,5 +1,6 @@ package client.scenes.recipe; +import client.utils.DefaultValueFactory; import client.utils.LocaleAware; import client.utils.LocaleManager; import com.google.inject.Inject; @@ -7,6 +8,8 @@ import commons.Recipe; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; + +import commons.RecipeIngredient; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; @@ -40,15 +43,15 @@ public class IngredientListCtrl implements LocaleAware { * changes and update the recipe accordingly. *

*/ - private ObservableList ingredients; + private ObservableList ingredients; /** * A callback function that is called when the ingredient list is updated. */ - private Consumer> updateCallback; + private Consumer> updateCallback; @FXML - public ListView ingredientListView; + public ListView ingredientListView; @FXML public Label ingredientsLabel; @@ -69,7 +72,7 @@ public class IngredientListCtrl implements LocaleAware { if (recipe == null) { this.ingredients = FXCollections.observableArrayList(new ArrayList<>()); } else { - List ingredientList = recipe.getIngredients(); + List ingredientList = recipe.getIngredients(); this.ingredients = FXCollections.observableArrayList(ingredientList); } @@ -82,7 +85,7 @@ public class IngredientListCtrl implements LocaleAware { * * @param callback The function to call upon each update. */ - public void setUpdateCallback(Consumer> callback) { + public void setUpdateCallback(Consumer> callback) { this.updateCallback = callback; } @@ -99,7 +102,7 @@ public class IngredientListCtrl implements LocaleAware { * @param event The action event. */ private void handleIngredientAdd(ActionEvent event) { - this.ingredients.add("Ingredient " + (this.ingredients.size() + 1)); + this.ingredients.add(DefaultValueFactory.getDefaultFormalIngredient()); this.refresh(); this.updateCallback.accept(this.ingredients); @@ -112,9 +115,9 @@ public class IngredientListCtrl implements LocaleAware { * * @param event The edit event. */ - private void handleIngredientEdit(EditEvent event) { + private void handleIngredientEdit(EditEvent event) { int index = event.getIndex(); - String newValue = event.getNewValue(); + RecipeIngredient newValue = event.getNewValue(); this.ingredients.set(index, newValue); this.refresh(); @@ -154,9 +157,6 @@ public class IngredientListCtrl implements LocaleAware { @Override public void initializeComponents() { - // TODO: set up communication with the server - // this would probably be best done with the callback (so this class doesn't - // interact with the server at all) this.ingredientListView.setEditable(true); this.ingredientListView.setCellFactory( list -> { diff --git a/client/src/main/java/client/scenes/recipe/OrderedEditableListCell.java b/client/src/main/java/client/scenes/recipe/OrderedEditableListCell.java index 2a4b861..01b1e01 100644 --- a/client/src/main/java/client/scenes/recipe/OrderedEditableListCell.java +++ b/client/src/main/java/client/scenes/recipe/OrderedEditableListCell.java @@ -2,6 +2,12 @@ package client.scenes.recipe; import javafx.scene.control.ListCell; +/** + * The abstract class that pre-defines common features between + * {@link IngredientListCell IngredientListCell} and + * {@link RecipeStepListCell RecipeStepListCell}. + * @param The type of data the list cell contains. + */ public abstract class OrderedEditableListCell extends ListCell { /** * Get the display text for the given item, prefixed with its index. @@ -23,6 +29,10 @@ public abstract class OrderedEditableListCell extends ListCell { @Override public void startEdit() { + super.startEdit(); TextField textField = new TextField(this.getItem()); // Commit edit on Enter key press diff --git a/client/src/main/java/client/utils/DefaultRecipeFactory.java b/client/src/main/java/client/utils/DefaultRecipeFactory.java deleted file mode 100644 index 0a94786..0000000 --- a/client/src/main/java/client/utils/DefaultRecipeFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package client.utils; - -import commons.Recipe; - -import java.util.List; - -public class DefaultRecipeFactory { - public static Recipe getDefaultRecipe() { - return new Recipe( - null, - "Untitled recipe", - List.of(), - List.of()); - } -} diff --git a/client/src/main/java/client/utils/DefaultValueFactory.java b/client/src/main/java/client/utils/DefaultValueFactory.java new file mode 100644 index 0000000..f0d7ff6 --- /dev/null +++ b/client/src/main/java/client/utils/DefaultValueFactory.java @@ -0,0 +1,57 @@ +package client.utils; + +import commons.FormalIngredient; +import commons.Ingredient; +import commons.Recipe; +import commons.Unit; +import commons.VagueIngredient; + +import java.util.List; + +/** + * A factory for default values used in the client UX flow. + * @see DefaultValueFactory#getDefaultRecipe() + * @see DefaultValueFactory#getDefaultFormalIngredient() + * @see DefaultValueFactory#getDefaultVagueIngredient() + */ +public class DefaultValueFactory { + /** + * Instantiates a recipe with a default name and null-ID. + * The ID is null such that it can be updated + * as appropriate on the backend. + * @return The default recipe. + */ + public static Recipe getDefaultRecipe() { + return new Recipe( + null, + "Untitled recipe", + List.of(), + List.of()); + } + + /** + * Instantiates a default formal ingredient with a default name and default ID. + * Note in particular the ID being 0L, see IngredientController + * server-side implementation for more details on safeguards. + * @return The default formal ingredient. + */ + public static FormalIngredient getDefaultFormalIngredient() { + return new FormalIngredient( + new Ingredient(0L, "default ingredient", 0., 0., 0.), + 0.0, + Unit.GRAMME.suffix + ); + } + + /** + * Instantiates a vague ingredient. Not currently in use. + * @see DefaultValueFactory#getDefaultFormalIngredient() + * @return The vague ingredient. + */ + public static VagueIngredient getDefaultVagueIngredient() { + return new VagueIngredient( + new Ingredient(null, + "default ingredient", 0., 0., 0.), + "Some"); + } +} diff --git a/client/src/main/java/client/utils/ServerUtils.java b/client/src/main/java/client/utils/ServerUtils.java index 2d6dc46..9894c80 100644 --- a/client/src/main/java/client/utils/ServerUtils.java +++ b/client/src/main/java/client/utils/ServerUtils.java @@ -3,6 +3,7 @@ package client.utils; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import commons.Recipe; +import commons.RecipeIngredient; import jakarta.ws.rs.ProcessingException; import jakarta.ws.rs.client.ClientBuilder; import org.glassfish.jersey.client.ClientConfig; @@ -170,8 +171,8 @@ public class ServerUtils { } - public void addRecipeIngredient(Recipe recipe, String ingredient) throws IOException, InterruptedException { - List ingredients = new ArrayList<>(recipe.getIngredients()); + public void addRecipeIngredient(Recipe recipe, RecipeIngredient ingredient) throws IOException, InterruptedException { + List ingredients = new ArrayList<>(recipe.getIngredients()); ingredients.add(ingredient); recipe.setIngredients(ingredients);