Merge branch 'client/feature-servings' into 'main'
feat(client/servings): inferring serving size from user input Closes #72 See merge request cse1105/2025-2026/teams/csep-team-76!74
This commit is contained in:
commit
23d6d6bbf6
4 changed files with 45 additions and 28 deletions
|
|
@ -49,6 +49,8 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
|
|
||||||
public Spinner<Double> scaleSpinner;
|
public Spinner<Double> scaleSpinner;
|
||||||
public Label inferredKcalLabel;
|
public Label inferredKcalLabel;
|
||||||
|
public Spinner<Integer> servingsSpinner;
|
||||||
|
public Label inferredServeSizeLabel;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private IngredientListCtrl ingredientListController;
|
private IngredientListCtrl ingredientListController;
|
||||||
|
|
@ -157,7 +159,7 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
|
|
||||||
// If there is a scale
|
// If there is a scale
|
||||||
// Prevents issues from first startup
|
// Prevents issues from first startup
|
||||||
if (scaleSpinner.getValue() != null) {
|
if (scaleSpinner.getValue() != null && servingsSpinner.getValue() != null) {
|
||||||
Double scale = scaleSpinner.getValue();
|
Double scale = scaleSpinner.getValue();
|
||||||
// see impl. creates a scaled context for the recipe such that its non-scaled value is kept as a reference.
|
// see impl. creates a scaled context for the recipe such that its non-scaled value is kept as a reference.
|
||||||
this.recipeView = new ScalableRecipeView(recipe, scale);
|
this.recipeView = new ScalableRecipeView(recipe, scale);
|
||||||
|
|
@ -167,6 +169,10 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
Double.isNaN(this.recipeView.scaledKcalProperty().get()) ?
|
Double.isNaN(this.recipeView.scaledKcalProperty().get()) ?
|
||||||
0.0 : this.recipeView.scaledKcalProperty().get())
|
0.0 : this.recipeView.scaledKcalProperty().get())
|
||||||
, this.recipeView.scaledKcalProperty()));
|
, this.recipeView.scaledKcalProperty()));
|
||||||
|
recipeView.servingsProperty().set(servingsSpinner.getValue());
|
||||||
|
inferredServeSizeLabel.textProperty().bind(Bindings.createStringBinding(
|
||||||
|
() -> String.format("Inferred size per serving: %.1f g", recipeView.servingSizeProperty().get()),
|
||||||
|
recipeView.servingSizeProperty()));
|
||||||
// expose the scaled view to list controllers
|
// expose the scaled view to list controllers
|
||||||
this.ingredientListController.refetchFromRecipe(this.recipeView.getScaled());
|
this.ingredientListController.refetchFromRecipe(this.recipeView.getScaled());
|
||||||
this.stepListController.refetchFromRecipe(this.recipeView.getScaled());
|
this.stepListController.refetchFromRecipe(this.recipeView.getScaled());
|
||||||
|
|
@ -402,6 +408,14 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
// triggers a UI update each time the spinner changes to a different value.
|
// triggers a UI update each time the spinner changes to a different value.
|
||||||
setCurrentlyViewedRecipe(recipe);
|
setCurrentlyViewedRecipe(recipe);
|
||||||
});
|
});
|
||||||
|
servingsSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, Integer.MAX_VALUE, 1));
|
||||||
|
servingsSpinner.setEditable(true);
|
||||||
|
servingsSpinner.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setCurrentlyViewedRecipe(recipe);
|
||||||
|
});
|
||||||
langSelector.getItems().addAll("en", "nl", "pl", "tok");
|
langSelector.getItems().addAll("en", "nl", "pl", "tok");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,19 @@ import commons.Recipe;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.ObjectBinding;
|
import javafx.beans.binding.ObjectBinding;
|
||||||
import javafx.beans.property.DoubleProperty;
|
import javafx.beans.property.DoubleProperty;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleDoubleProperty;
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
|
||||||
public class ScalableRecipeView {
|
public class ScalableRecipeView {
|
||||||
private final ObjectProperty<Recipe> recipe = new SimpleObjectProperty<>();
|
private final ObjectProperty<Recipe> recipe = new SimpleObjectProperty<>();
|
||||||
private final ObjectProperty<Recipe> scaled = new SimpleObjectProperty<>();
|
private final ObjectProperty<Recipe> scaled = new SimpleObjectProperty<>();
|
||||||
private final DoubleProperty scale = new SimpleDoubleProperty();
|
private final DoubleProperty scale = new SimpleDoubleProperty();
|
||||||
private final SimpleDoubleProperty scaledKcal = new SimpleDoubleProperty();
|
private final DoubleProperty scaledKcal = new SimpleDoubleProperty();
|
||||||
|
private final IntegerProperty servings = new SimpleIntegerProperty();
|
||||||
|
private final DoubleProperty servingSize = new SimpleDoubleProperty();
|
||||||
public ScalableRecipeView(
|
public ScalableRecipeView(
|
||||||
Recipe recipe,
|
Recipe recipe,
|
||||||
Double scale
|
Double scale
|
||||||
|
|
@ -24,10 +28,10 @@ public class ScalableRecipeView {
|
||||||
this.recipe, this.scale);
|
this.recipe, this.scale);
|
||||||
this.scaled.bind(binding);
|
this.scaled.bind(binding);
|
||||||
this.scaledKcal.bind(Bindings.createDoubleBinding(() -> this.scaled.get().kcal(), this.scaled));
|
this.scaledKcal.bind(Bindings.createDoubleBinding(() -> this.scaled.get().kcal(), this.scaled));
|
||||||
}
|
this.servingSize.bind(Bindings.createDoubleBinding(
|
||||||
|
() -> this.scaled.get().weight() * ( 1.0 / this.servings.get()),
|
||||||
public double getScale() {
|
this.servings)
|
||||||
return scale.get();
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Recipe getRecipe() {
|
public Recipe getRecipe() {
|
||||||
|
|
@ -38,23 +42,14 @@ public class ScalableRecipeView {
|
||||||
return scaled.get();
|
return scaled.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getScaledKcal() {
|
public DoubleProperty scaledKcalProperty() {
|
||||||
return scaledKcal.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public DoubleProperty scaleProperty() {
|
|
||||||
return scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectProperty<Recipe> scaledProperty() {
|
|
||||||
return scaled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectProperty<Recipe> recipeProperty() {
|
|
||||||
return recipe;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SimpleDoubleProperty scaledKcalProperty() {
|
|
||||||
return scaledKcal;
|
return scaledKcal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IntegerProperty servingsProperty() {
|
||||||
|
return servings;
|
||||||
|
}
|
||||||
|
public DoubleProperty servingSizeProperty() {
|
||||||
|
return servingSize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,15 @@
|
||||||
<Button fx:id="removeRecipeButton" mnemonicParsing="false" onAction="#removeSelectedRecipe" text="Remove Recipe" />
|
<Button fx:id="removeRecipeButton" mnemonicParsing="false" onAction="#removeSelectedRecipe" text="Remove Recipe" />
|
||||||
<Button fx:id="printRecipeButton" mnemonicParsing="false" onAction="#printRecipe" text="Print Recipe" />
|
<Button fx:id="printRecipeButton" mnemonicParsing="false" onAction="#printRecipe" text="Print Recipe" />
|
||||||
<Button fx:id="favouriteButton" onAction="#toggleFavourite" text="☆" />
|
<Button fx:id="favouriteButton" onAction="#toggleFavourite" text="☆" />
|
||||||
<Label>Scale: </Label>
|
|
||||||
<Spinner fx:id="scaleSpinner" />
|
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
<ComboBox fx:id="langSelector" onAction="#changeLanguage" />
|
<HBox>
|
||||||
|
<ComboBox fx:id="langSelector" onAction="#changeLanguage" />
|
||||||
|
<Label>Scale: </Label>
|
||||||
|
<Spinner fx:id="scaleSpinner" />
|
||||||
|
<Label>Servings: </Label>
|
||||||
|
<Spinner fx:id="servingsSpinner" />
|
||||||
|
</HBox>
|
||||||
|
|
||||||
<!-- Ingredients -->
|
<!-- Ingredients -->
|
||||||
<fx:include source="RecipeIngredientList.fxml" fx:id="ingredientList" />
|
<fx:include source="RecipeIngredientList.fxml" fx:id="ingredientList" />
|
||||||
|
|
@ -37,5 +41,6 @@
|
||||||
<!-- Preparation -->
|
<!-- Preparation -->
|
||||||
<fx:include source="RecipeStepList.fxml" fx:id="stepList"
|
<fx:include source="RecipeStepList.fxml" fx:id="stepList"
|
||||||
VBox.vgrow="ALWAYS" maxWidth="Infinity" />
|
VBox.vgrow="ALWAYS" maxWidth="Infinity" />
|
||||||
|
<Label fx:id="inferredServeSizeLabel" />
|
||||||
<Label fx:id="inferredKcalLabel" />
|
<Label fx:id="inferredKcalLabel" />
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,10 @@ public class Recipe {
|
||||||
final double PER = 100; // Gram
|
final double PER = 100; // Gram
|
||||||
return
|
return
|
||||||
this.ingredients.stream().mapToDouble(RecipeIngredient::getKcal).sum() /
|
this.ingredients.stream().mapToDouble(RecipeIngredient::getKcal).sum() /
|
||||||
this.ingredients.stream().mapToDouble(RecipeIngredient::getBaseAmount).sum() * PER;
|
weight() * PER;
|
||||||
|
}
|
||||||
|
public double weight() {
|
||||||
|
return this.ingredients.stream().mapToDouble(RecipeIngredient::getBaseAmount).sum();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue