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);